摘要:函數式編程函數式,可能并不是那么難。在學習函數式編程之初,首先要知道在這一技能葉子中包含有多少個相關詞,其次要知道它和我們是否從未有過遇見。
JS函數式編程
函數式,可能并不是那么難。
在學習JS函數式編程之初,首先要知道在這一“技能葉子”中包含有多少個相關詞,其次要知道它和我們是否從未有過遇見。
一等公民、純函數、柯里化、代碼組合、pointfree、命令式與申明式、
Hindley-Milner類型簽名、“特百惠”(Container、functor、Maybe、Either)、lift
Monad(pointed functor、chain)、Applicative Functor
接下來,我將根據JS函數式編程說說自己對每個相關詞的看法。
一等公民(將函數與數字做平等對待)// 數字 var x = 1; var y = x; // 函數, 不平等對待 var fx = function(x) { return x; } var fy = function(y) { return fx(y); } // 函數,平等對待 // ... var fy = fx;
在編程之初,我們就將函數與其他數據類型從思想上分隔開,但在函數式編程中,我們要摒棄這種思想,將函數和其他數據類型做相等看待。
讓我們來看看下面這個是否將函數當做一等公民?
var fz = function(f){ return fy(function(y){ return f(y); }); }
上述代碼讓我們看的很繞是吧,那我們將其轉換一下如何?
var fz = function(f){ var fx = f; return fy(function(y){ return fx(y) }) }
根據之前的 fy = fx 等式便可知 function(y){return fx(y)} 其實就是等于 fx 的,所以就可以轉化成
var fz = function(f){ return fy(f); } // 同上,fz 即等于 fy var fz = fy;
于是乎,這就是一等公民的函數。思想切莫先入為主。
純函數純函數是一種函數,即相同的輸入,永遠得到相同的輸出。正如 O = kI + b,IO在數學上的函數關系。(O: 輸出 , I: 輸入)
函數式編程追求的是純函數
var xs = [1,2,3,4,5]; // 純 xs.slice(0,3); // => [1,2,3] xs.slice(0,3); // => [1,2,3] // 不純 xs.splice(0,3); // => [1,2,3] xs.splice(0,3); // => [4,5]純函數的好處
可緩存性、可移植性、可測試性、合理性、并行代碼
這些好處都可依據“純函數不會隨外部環境的改變而改變其內部運算邏輯”而得出。
概念:只傳遞給函數我們需要傳遞的所有參數的一部分,讓它返回一個函數去處理剩下的參數。
var add = function(x){ return function(y){ return x + y; } } var addOne = add(1); var addTen = add(10); addOne(2); // => 3 addTen(2); // => 12
柯里化很好的體現了純函數一個輸入對應一個輸出的概念,柯里化就是每傳遞一個參數,就返回一個新函數處理剩下的參數。
代碼組合組合(compare), 就像工廠流水線一樣,將多個函數按順序拼湊,從右到左 依次加工數據
var compare = function(f, g){ return function(x){ return f(g(x)); } }
下面是一個反轉數組取第一個元素得到其首字母并大寫的例子
var reduce = (f) => (arr) => arr.reduce(f) var reverse = reduce(function(src, next){return [next].concat(src)}, []); var head = (x) => x[0] var toUpperCase = (x) => x.toUpperCase(); // 記住是從右向左 var f = compare(compare(compare(head,toUpperCase), head), reverse); f(["abc","def","ghi"]) // => G
pointfree就是函數組合,而這些函數包含一等公民與柯里化的概念。
申明式與命令式作個對比就能知道這兩個的區別了
// 命令式 var arr1 = [1,2,3,4,5]; var arr2 = []; for(let i = 0; i < arr1.length; i++) { arr.push(arr1[i]); } // 申明式 arr2 = arr1.map(function(c){return c;})
命令式是那種一步接著一步的執行方式,而申明式是并行運算,正如上述的代碼組合的例子一樣,如果我們用命令式來寫肯定是一句一個邏輯,寫起來看起來都很費勁,但是申明式不同,它能讓我們只使用一次就可執行多條邏輯,而且可以在不同情況環境下重復使用,這就是純函數的可移植性。
再看一個例子
// 命令式 var headToUpperCase = function(str){ var h = head(str); return h.toUpperCase(); } // 聲明式 var headToUpperCase = compare(toUpperCase, head);Hindley-Milner類型簽名
此玩意就是對你構造的函數進行一個說明
// 例一 // 下面就是Hindley-Milner類型簽名 // strLength :: String -> Number var strLength = function(str){ return str.length; } // 例二 // join :: String -> [String] -> String var join = curry(function(what, xs){ return xs.join(what); }) // curry就是將傳入的函數參數轉換成curry函數并返回 // 第一個String指代what, [string]指代xs,第二個string指代return的值 // 例三 // concat :: a -> b -> c var concat = curry(function(src, next){ return src.concat(next); }) // a,b可以用任何字母代替,但不能相同,這樣表示的是不同的類型,而不是從同一數據中脫離出來,如去數組中的某幾個元素組成新的數組 // 例四 // map :: (a -> b) -> [a] -> [b] var map = curry(function(f, xs){ return xs.map(f); }) // a -> b 指代 f ; [a] 指代 xs ; [b] 指代 return 的值 // 例五 // reduce :: (a -> b -> a) -> b -> [a] -> b var reduce = curry(function(f, x, xs){ return xs.reduce(f, x) })
下面會介紹兩個functor(Container, Maybe)
Container先給源碼
var Container = function(){ this.__value = x; } Contaienr.of = function(x) { return new Container(x); } Container.map = function(f) { return Container.of(f(this.__value)) }
使用Container將我們的值進行包裹
使用Container.of讓我們不用寫new
使用Container.map讓我們在不訪問__value的情況下得到容器內部的值并進行運算
同樣,先給源碼
var Maybe = function(x){ this.__value = x; } Maybe.of = function(x){ return new Maybe(x); } Maybe.prototype.isNothing = function(){ return (this.__value === null || this.__value === undefined); } Maybe.prototype.map = function(f){ return this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.__value)); }
Maybe 和 Container其實差不多,唯一的區別在于它出現了對空值的檢測,讓容器現在能夠存儲空值了。
Either(Left and Right)
先上源碼
var Left = function(x){ this.__value = x; } Left.of = function(x){ return new Left(x); } Left.prototype.map = function(f){ return this; } var Right = function(x){ this.__value = x; } Right.of = function(x){ return new Right(x); } Right.prototype.map = function(f){ return Right.of(f(this.__value)) }
跟Maybe(null) 一樣,當返回一個Left 時就直接讓程序短路,但有了Left 至少可以讓我們用if 做一次條件判斷來知道是什么情況導致輸出Left
lift
一個函數在調用的時候,如果被map包裹從一個非functor函數轉換為一個functor函數,就叫做lift。這樣讓普通函數變成可以操作容器的函數,且兼容任意functor。
以下是例子
var headToUpperCase = map(compare(toUpperCase, head)); headToUpperCase(Container.of("hello!"));IO
var IO = function(f){ this.__value = f; } IO.of = function(){ return new IO(function(){ return x; }) } IO.prototype.map = function(f){ return new IO(compose(f, this.__value)); }
現在this.__value 是一個函數,因而如果執行map(head) 等操作時其實是將這個函數壓入一個“執行棧”,而這棧中全部是要執行的函數,就想是代碼組合一樣,將所有壓入的函數延遲執行。而看起來,我們容器的最終形態就是能容納一個函數。
那么問題就來了,為什么要用容器,而且最好是容納函數呢?
函數式程序即通過管道把數據在一系列純函數間傳遞的程序,而我們之前所有的例子都是關于同步編碼的,如果出現異步情況怎么辦?如下:
var fs = require("fs"); var readFile = function(filename){ return function(reject, result){ fs.readFile(filename, "utf-8", function(reject, result){ err ? reject(err) : result(data); }) } }
ok ,這的確使用了函數式,但異步之后呢,依舊是回調階梯,所以這么做并沒有真正意義上的使用函數式。
我們需要延遲執行,因而我們需要一個類似IO但并非IO的容器類型,由于能力有限,我只能借用Quildreen Motta 所處理的Folktale 里的Data.Task
var fs = require("fs"); var readFile = function(filename){ return new Task(function(reject, result){ fs.readFile(filename, "utf-8", function(err, data){ err ? reject(err) : result(data); }); }) } readFile("helloworld").map(split(" ")).map(head).map(toUpperCase).map(head); // => Task("H")Monad
先給例子
var fs = require("fs"); // readFile :: String -> IO String var readFile = function(filename){ return new IO(function(){ return fs.readFileSync(filename, "utf-8"); }) } // print :: String -> IO String var print = function(x){ return new IO(function(){ return x }) } var hello = compose(map(print), readFile); hello("helloworld"); // => IO(IO("helloworld")) // 包了兩層IO,于是要想得到值,我們就得執行兩次__value hello("helloworld").__value().__value(); // => helloworld
那么如何才能消去這多的層數呢,我們需要使用join
IO.prototype.isNothing = function(){ return (this.__value === null || this.__value === undefined); } IO.prototype.join = function(){ return this.isNothing() ? IO.of(null) : this.__value; } var ioio = IO.of(IO.of("hello")); // => IO(IO("hello")) ioio.join(); // => IO("hello")
于是我們在map 之后就要使用 join ,讓我們將其叫做chain
var chain = curry(function(f, m){ return m.map(f).join(); // or compose(join, map(f))(m) }) // map/join var hello = compose(join, map(print), readFile); // chain var hello = compose(chain(print), readFile); // 給Maybe也加上chain Maybe.of(3).chain(function(three){ return Maybe.of(2).map(add(three)) }) // => Maybe(5);applicative functor
如下實例
var add = curry(function(x, y){ return x + y; }) add(Container.of(2), Container.of(3)); // 很明顯是不能這么進行計算的 // 但是用chain,我們可以 Container.of(2).chain(function(two){ return Container.of(3).map(add(two)) })
可是這看起來挺費勁的不是嗎
于是我們就要使用applicative functor
Container.prototype.ap = function(other_container){ return other_container.map(this.__value); } Container.of(2).map(add).ap(Container.of(3));
ap 就是一種函數,能夠把一個functor的函數值應用到另一個functor的值上。
而根據上述例子,我們可知map 是等價于 of/ap 的
F.prototype.map = function(f){ return this.constructor.of(f).ap(this); }
而chain 則可以分別得到 functor 和 applicative
// map F.prototype.map = function(){ var ct = this; return ct.chain(function(a){ return ct.constructor.of(f(a)) }) } // ap F.prototype.ap = function(other){ return this.chain(function(f){ return other.map(f); }) }定律
代碼組合的定律
// 結合律 var _bool = compose(f, compose(g, h)) == compose(compose(f, g), h); // => true
map的組合律
var _bool = compose(map(f), map(g)) == map(compose(f, g)) // => true
Monad
// 結合律 var _bool = compose(join, map(join)) == compose(join, join) // => true // 同一律 compose(join, of) == compose(join, map(of)) var mcompose = function(f, g){ return compose(chain(f), chain(g)) } // 左同一律 mcompose(M, f) == f // 右同一律 mcompose(f, M) == f // 結合律 mcompose(mcompose(f,g), h) == mcompose(f, mcompose(g, h))
Applicative Functor
var tOfM = compose(Task.of, Maybe.of); tOfM("hello").map(concat).ap(tOfM(" world"))); // => Task(Maybe(hello world)) // 同一律 A.of(id).ap(v) == v // 同態 A.of(f).ap(A.of(x)) == A.of(f(x)) // 互換 var v = Task.of(reverse) var x = "olleh" v.ap(A.of(x)) == A.of(function(f){return f(x)}).ap(v) // 組合 var u = IO.of(toUpper) var v = IO.of(concat(" world")) var w = IO.of("hello") IO.of(compose).ap(u).ap(v).ap(w) == u.ap(v.ap(w))參考鏈接
https://www.gitbook.com/book/...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/88354.html
摘要:在創業初期,你招來的工程師必須是能夠獨當一面的大神隊友。要評估一個應聘者的真實水準,最佳方式就是結對編程。用微博的抓取消息并顯示在時間線上,就是個很好的考察應聘者的面試項目。不過結對編程再好使,也沒辦法讓你完全了解一個應聘者。 原文鏈接:10 Interview Questions Every JavaScript Developer Should Know 對大部分公司來說,招聘技...
摘要:前端面試總結先說背景,本人年月畢業,去年十月校招到今年月一直在做前端開發工作,年前打算換工作,就重新梳理下面試考點總結包含基礎,基礎,常見算法和數據結構,框架,計算機網絡相關知識,可能有的點很細,有的點很大,參考個人情況進行總結,方便對知識 前端面試總結 先說背景,本人2018年7月畢業,去年十月校招到今年10月一直在做前端開發工作,年前打算換工作,就重新梳理下面試考點總結包含: ...
摘要:前端面試總結先說背景,本人年月畢業,去年十月校招到今年月一直在做前端開發工作,年前打算換工作,就重新梳理下面試考點總結包含基礎,基礎,常見算法和數據結構,框架,計算機網絡相關知識,可能有的點很細,有的點很大,參考個人情況進行總結,方便對知識 前端面試總結 先說背景,本人2018年7月畢業,去年十月校招到今年10月一直在做前端開發工作,年前打算換工作,就重新梳理下面試考點總結包含: ...
摘要:為目前使用范圍最廣的網絡保護協議。身處攻擊目標周邊的惡意人士能夠利用密鑰重裝攻擊,利用此類安全漏洞。本文和大家一起探討下如何在三年內快速成長為一名技術專家。 業界動態 Vue 2.5 released Vue 2.5 正式發布,作者對于該版本的優化總結:更好的TypeScript 集成,更好的錯誤處理,更好的單文件功能組件支持以及更好的與環境無關的SSR WiFi爆驚天漏洞!KRACK...
閱讀 1904·2021-11-09 09:46
閱讀 2486·2019-08-30 15:52
閱讀 2445·2019-08-30 15:47
閱讀 1320·2019-08-29 17:11
閱讀 1746·2019-08-29 15:24
閱讀 3501·2019-08-29 14:02
閱讀 2442·2019-08-29 13:27
閱讀 1199·2019-08-29 12:32