最近一直在學習函數式編程,前面介紹了函數式編程中很是重要的兩個運算函數柯里化 和 函數組合,下文出現的curry
和 compose
函數能夠從前兩篇文章中找到。它們均可以直接在實際開發中用到,寫出函數式的程序。javascript
本文主要初探容器的相關概念,以及如何處理編程中異步操做,錯誤處理等依賴外部環境狀態變化的狀況,,html
容器能夠想象成一個瓶子,也就是一個對象,裏面能夠放各類不一樣類型的值。想一想,瓶子還有一個特色,跟外界隔開,只有從瓶口才能拿到裏面的東西;類比看看, container 回暴露出接口供外界操做內部的值。java
一個典型的容器示例:git
var Container = function(x) { this.__value = x; } Container.of = function(x) { return new Container(x); } Container.of("test") // 在chrome下會打印出 // Container {__value: "test"}
咱們已經實現了一個容器,而且實現了一個把值放到容器裏面的 Container.of
方法,簡單看,它像是一個利用工廠模式建立特定對象的方法。of
方法正是返回一個container。web
上面容器上定義了of
方法,functor
的定義也相似chrome
Functor 是實現了
map
函數並遵照一些特定規則的容器類型。
把值留在容器中,只能暴露出map
接口處理它。函子是很是重要的數據類型,後面會講到各類不一樣功能的函子,對應處理各類依賴外部變量狀態的問題。編程
Container.prototype.map = function(f) { return Container.of(f(this.__value)) }
把即將處理容器內變量的函數,包裹在map
方法裏面,返回的執行結果也會是一個Container。
這樣有幾點好處:segmentfault
// 利用上一篇中講到的柯里化,就能夠看出其特性。 var add2 = function(x, y) { return x + y; }; curriedAdd = curry(add2); Container.of(2).map(curriedAdd(3)); // Container {__value: 5}
容器在處理內部值時,常常遇到傳入參數異常的狀況的狀況,檢查value 值的合理性就很是重要。Maybe 函子保證在調用傳入的函數以前,檢查值是否爲空。數組
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)); }
這個通用的例子,體現了輸出結果的不肯定性,也能夠看出,在容器內部全部對value值的操做,都會交給容器來執行。在value 爲空的狀況下,也會返回包裹着null的容器,避免後續容器鏈式調用報錯。dom
一般 使用throw/catch
就能夠捕獲異常,拋出錯誤,但它並非一種純函數方法,最好的方法是在出現異常時,能夠正常返回信息。Either
函子,內部兩個子類Left
和 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)); } // 輸入數據進行校驗 var setage = function(age) { return typeof age === 'number'? Right.of(age): Left.of('error age') } setage(12).map(function(age){return 'my age is' + age}) // Right {__value: "my age is12"} setage("age").map(function(age){return 'my age is' + age}) // Left {__value: "error age"}
left
和 right
惟一的區別在於map 方法的實現,固然,一個函子最大的特色也體如今map方法上,Left.map
無論傳入的是什麼函數,直接返回當前容器;Right.map
則是示例裏面的方法同樣。
IO 操做自己就是不純的操做,生來就得跟外界環境變量打交道,不過能夠掩蓋他的不肯定性。跟下面localStorage
包裹函數相似,延遲執行IO 操做。
var getStorage = function(key) { return function() { return localStorage[key]; } }
再看看,封裝了高級一點的IO 函子:
var IO = function(f) { this.__value = f; } IO.of = function(x) { return new IO(function(){ return x; }) } IO.prototype.map = function(f) { // 使用上一句定義的compose函數 return new IO(compose(f, this.__value)) }
compose函數組合,裏面存放的都是函數,this.__value
跟其餘函子內部值不一樣,它是函數。IO.of
方法在new
對象以前,把值包裹在函數裏面,試圖延遲執行。
// 測試一下 var io__dom= new IO(function() {return window.document}) io__dom.map(function(doc) { return doc.title}) // IO {__value: ƒ}
返回一個沒有執行的函數對象,裏面的__value
值對應的函數,在上面函數調用後並無執行,只有在調用了this.__value
值後,才執行。最後一步不純的操做,交給了函數調用者去作。
一個functor, 只要他定義了一個join 方法和一個of 方法,那麼它就是一個monad。 它能夠將多層相同類型的嵌套扁平化,像剝洋蔥同樣。關鍵在於它比通常functor 多了一個join
方法。 咱們先看看剝開一層的join
方法。
var IO = function(f) { this.__value = f } IO.of = function(x) { return new IO(function(){ return x; }) } IO.prototype.join = function() { return this.__value ? this.__value(): IO.of(null); } // 包裹上兩層 var foo = IO.of(IO.of('test bar')); foo.join().__value(); // 返回裏面嵌套着的IO類。 IO {__value: ƒ},接着只需調用這裏的__value(),就能夠返回字符串`test bar`;
回頭看看前面map
方法,return new IO()
,生成新的容器,方便鏈式調用,跟 join
方法結合一塊兒使用,生成容器後,再扁平化。造成 chain 函數,
var chain = curry(function(f, m) { return m.map(f).join(); })
看一個完整示例,其中curry 和compose,分別用到了連接裏面的實現,:
var IO = function(f) { this.__value = f; } IO.of = function(x) { return new IO(function() { return x; }) } IO.prototype.map = function(f) { // 使用上一句定義的compose函數 return new IO(compose(f, this.__value)) } IO.prototype.join = function() { return this.__value ? this.__value() : IO.of(null); } var chain = curry(function(f, m) { return m.map(f).join(); }) var log = function(x) { return new IO(function() { console.log(x); return x; }) } var setStyle = curry(function(sel, props) { return new IO(function() { return document.querySelector(sel).style.background = props }) }) var getItem = function(key) { return new IO(function() { return localStorage.getItem(key); }) }; var map = curry(function(f, functor) { return functor.map(f); }); // 簡單實現join var join = function(functor) { return functor.join(); } localStorage.background = '#000'; var setItemStyle = compose(join, map(setStyle('body')), join, map(log), getItem); // 換成 鏈式調用。 setItemStyle = compose(chain(setStyle('body')), chain(log), getItem); setItemStyle('background').__value(); // 操做dom 改變背景顏色
本文主要利用簡單代碼舉例,介紹了容器,函子等相關概念,初步認識了各類不一樣的函子。深刻實踐示例,能夠參考閱讀下面連接: