函數式編程 - 容器(container)

最近一直在學習函數式編程,前面介紹了函數式編程中很是重要的兩個運算函數柯里化函數組合,下文出現的currycompose函數能夠從前兩篇文章中找到。它們均可以直接在實際開發中用到,寫出函數式的程序。javascript

本文主要初探容器的相關概念,以及如何處理編程中異步操做錯誤處理等依賴外部環境狀態變化的狀況,,html

容器(container)

容器能夠想象成一個瓶子,也就是一個對象,裏面能夠放各類不一樣類型的值。想一想,瓶子還有一個特色,跟外界隔開,只有從瓶口才能拿到裏面的東西;類比看看, 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

函子(functor)

上面容器上定義了of方法,functor的定義也相似chrome

Functor 是實現了 map函數並遵照一些特定規則的容器類型。

把值留在容器中,只能暴露出map接口處理它。函子是很是重要的數據類型,後面會講到各類不一樣功能的函子,對應處理各類依賴外部變量狀態的問題。編程

Container.prototype.map = function(f) {
    return Container.of(f(this.__value))
}

把即將處理容器內變量的函數,包裹在map方法裏面,返回的執行結果也會是一個Container。
這樣有幾點好處:segmentfault

  1. 保證容器內的value一直不會暴露出去,
  2. 對value的操做方法最終會交給容器執行,能夠決定什麼時候執行。
  3. 方便鏈式調用
// 利用上一篇中講到的柯里化,就能夠看出其特性。
    var add2 = function(x, y) {
        return x + y;
    };
    
    curriedAdd = curry(add2);
    
    Container.of(2).map(curriedAdd(3));
    // Container {__value: 5}

不一樣類型的函子

maybe

容器在處理內部值時,常常遇到傳入參數異常的狀況的狀況,檢查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函子,內部兩個子類Leftright; 能夠當作右值是正常狀況下使用並返回的值,左值是操做簡單的默認值。

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"}

leftright 惟一的區別在於map 方法的實現,固然,一個函子最大的特色也體如今map方法上,
Left.map 無論傳入的是什麼函數,直接返回當前容器;Right.map則是示例裏面的方法同樣。

IO 操做

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值後,才執行。最後一步不純的操做,交給了函數調用者去作。

Monad

一個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();
    })

看一個完整示例,其中currycompose,分別用到了連接裏面的實現,:

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 改變背景顏色

總結

本文主要利用簡單代碼舉例,介紹了容器,函子等相關概念,初步認識了各類不一樣的函子。深刻實踐示例,能夠參考閱讀下面連接:

  1. 函數式編程風格
  2. js函數式編程指南https://llh911001.gitbooks.io...
  3. JavaScript函數式編程(二)
  4. JavaScript:函數式編程基本概念學習
  5. JS函數式編程 - 函子和範疇論
  6. javascript函數式編程之 函子(functor)
  7. 函數式編程入門教程
相關文章
相關標籤/搜索