函數(shù)式編程

我覺得學習函數(shù)式編程以案例來學習是最快的方法,我看了很多講解還是實操起來學的快,推薦函數(shù)式編程指南這本書,同時還要看ramda,我是一開始用的ramda這個工具,這個工具讓函數(shù)式寫起來更方便。因為這本書中運用到了ramda,所有我覺得有必要兩個同時看,看完書前幾張基本概念的理解后,等看到到案例了再去看ramda,這樣學起來就快了。如果網(wǎng)上找不到書的可以問我要。
demo大都是以react為案例來寫的

一些概念

容器

我的理解:有了這個容器,每次執(zhí)行函數(shù)一次就把值又放到容器里面了,再次操作再次取值再次放入進去,十分方便,

// Container就是這個容器
var Container = function(x) {
  this.__value = x;
}

Container.of = function(x) { return new Container(x); };

// 加了map后就是另外一種形式了叫做functor,
// functor 是實現(xiàn)了 map 函數(shù)并遵守一些特定規(guī)則的容器類型

// map傳入函數(shù),執(zhí)行完用of再次把值放入容器里面,
Container.prototype.map = function(f){
  return Container.of(f(this.__value))
}

// 這里就展示了兩次在map里面執(zhí)行返回數(shù)值到容器里面
Container.of("bos").map(concat("waw")).map(R.prop("length"));
// 結(jié)果 {__value: 6}

這里如果看不懂可以這樣理解

// 這是一次map
Container.of("bos").map(concat("waw"))
// 這里執(zhí)行map后 相當于變成了
var a = concat("waw")("bos");
Container.of(a);

// 這是兩次map
Container.of("bos").map(concat("waw")).map(R.prop("length"));
// 這里變成了
var a = concat("waw")("bos");  // 合并的字符串
// R是ramda語法
var len = R.prop("length")("wawbos")  // wawbos的length 是=>6;
Container.of(len);
// {__value: 6}

Maybe

這個是在容器里面添加了空值判斷,Maybe 會先檢查自己的值是否為空,然后才調(diào)用傳進來的函數(shù)。這樣我們在使用 map 的時候就能避免的空值了的麻煩了

var Maybe = function(x){
  this.__value = x;
}
Maybe.of = (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.of("absck kbas").map(match(/a/ig)); // => {"__value": ["a", "a"]}
Maybe.of(null).map(match(/a/ig)); // => {"__value": null}
Maybe.of({name: "brois"}).map(R.prop("age")).map(add(10)); // => {"__value": null}

純函數(shù)

純函數(shù)是一種相同的輸入,永遠會得到相同的輸出,而且沒有任何可觀察的副作用

var xs = [1,2,3,4,5];

// 純的
xs.slice(0,3);
//=> [1,2,3]

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]

xs.splice(0,3);
//=> []

// ----------------------

// 不純的
var minimum = 21;

var checkAge = function(age) {
  return age >= minimum;
};


// 純的
var checkAge = function(age) {
  var minimum = 21;
  return age >= minimum;
};

// 這種可以理解為不改變外部變量,或者不受外部變量而影響,

// ---------------------

// 不純的
var signUp = function(attrs) {
  var user = saveUser(attrs);
  welcomeUser(user);
};

var saveUser = function(attrs) {
    var user = Db.save(attrs);
    ...
};

var welcomeUser = function(user) {
    Email(user, ...);
    ...
};

// 純的
var signUp = function(Db, Email, attrs) {
  return function() {
    var user = saveUser(Db, attrs);
    welcomeUser(Email, user);
  };
};

var saveUser = function(Db, attrs) {
    ...
};

var welcomeUser = function(Email, user) {
    ...
};

副作用包含

  • 更改文件系統(tǒng)
  • 往數(shù)據(jù)庫插入記錄
  • 發(fā)送一個 http 請求
  • 可變數(shù)據(jù)
  • 打印/log
  • 獲取用戶輸入
  • DOM 查詢
  • 訪問系統(tǒng)狀態(tài)

IO

像這樣的雖然是IO操作,但是是純函數(shù)的方式,不會受外部的影響,

var getFromStorage = function(key) {
  return function() {
    return localStorage[key];
  }
}

一般不會用上面這種,而是像下面這樣,

var IO = function(f) {
  this.__value = f;
}

IO.of = function(x) {
  return new IO(function() {
    return x;
  });
}

IO.prototype.map = function(f) {
  return new IO(R.compose(f, this.__value));
}

IO 跟之前的 functor 不同的地方在于,它的 __value 總是一個函數(shù)
IO 把非純執(zhí)行動作包裹到函數(shù)里,目的是延遲執(zhí)行這個非純動作。

實際操作

var url = new IO(function () {return window.location.href;});

var toPairs = R.compose(R.map(R.split('=')), R.split('&'));
var fii = function(key) {
    return R.map(R.compose(R.concat(key), R.split('/')), url);
}
fii("地址:").__value(); // => 地址: path,xxxx

monad

一個 functor,只要它定義個了一個 join 方法和一個 of 方法,并遵守一些定律,那么它就是一個 monad
這個的場景比如當用到多層io的時候會出現(xiàn)map(map(f));這個時候其實無需嵌套一層的,直接返回里面的值就可以了,

var maybe = function(x){
        this._value = x;
      }
      maybe.of = function(x) {
        return new maybe(x)
      }
      maybe.prototype.map=function(f){
        return maybe.of(f(this._value));
      };
      maybe.prototype.isNothing = function(){
        return (this._value === null || this._value === undefined)
      }
      // 注意這里的join,返回了_value
      maybe.prototype.join = function(){
        return this.isNothing() ? Maybe.of(null) : this._value;
      }
      var safeProp = R.curry(function(x, obj) {
        return new maybe(obj[x]);
      });

      var safeHead = safeProp(0);

      var joinTest = function(mma){
        return mma.join()
      }

      var address = R.compose(
      joinTest,
      R.map(safeProp('street')),
      joinTest,
      R.map(safeHead),
      safeProp("address"))
      let arrs =  {
           address:
             [
               {
                 street: {name: 'Mulburry', number: 8402},
                 postcode: "WC2N"
               }
             ]
            };
      let a = address(arrs);
      // a => maybe{_value: {name: "Mulburry", number: 8402}}

了解了join,然后把map和join打包在一個函數(shù)里面,這個函數(shù)可以叫做chain或者flatMap,他的形式如下

var chain = curry(function(f, m){
  return m.map(f).join(); // 或者 R.compose(join, map(f))(m)
});

然后可以把上面的代碼優(yōu)化下

var address = R.compose(
    chain(safeProp('street')),
    chain(safeHead),
    safeProp("address"))
// 為什么chain里面直接用參數(shù)自帶的map而上面的是R.map,因為這里的參數(shù)是maybe,他的map方法和Ramda的原理是一樣的,

applicative functor

applicative functor 是實現(xiàn)了 ap 方法的 pointed functor
場景:把一個 functor 的函數(shù)值應(yīng)用到另一個 functor 的值上
比如:

R.add(Container.of(2), Container.of(3));

這種是不對的,add取不到里面的值,需要一個ap函數(shù)來把一個 functor 的函數(shù)值應(yīng)用到另一個 functor 的值上

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

Container.of(R.add(2)).ap(Container.of(3));

這里比較繞,做好心理準備,從左向右執(zhí)行,Container.of(R.add(2)),這里構(gòu)造了一個Container,里面的__value是R.add(2)這個函數(shù),然后它調(diào)用了ap方法,這個參數(shù)是Container.of(3),所以

Container.prototype.ap=function(f){

這里的 fContainer.of(3),然后 Container.of(3)調(diào)用了map方法,傳的參數(shù)是this.__value,注意這里重點,這個 this.__valueR.add(2) ; 因為是Container.of(R.add(2))調(diào)用的ap方法,

return f.map(this.__value)
}

然后 f.map(this.__value)這里調(diào)用的事

Container.prototype.map = function (f) {

這里的f就是 R.add(2)了,因為是Container.of(3)調(diào)用的map方法,所以這里的this.__value就是3了, 最后就成了 R.add(2)(3);

return Container.of(f(this.__value))
}

如果還是沒理解,在我注釋的地方console一下就明白了,

上面的方法還可以寫成
Container.of(2).map(R.add).ap(Container.of(3));

案例

demo1 遍歷數(shù)組返回虛擬dom

    // 定義好數(shù)據(jù)
    constructor(props){
        super(props)
        this.state = {
            data = [{
                id: 1,
                name: "王"
            }, {
                id: 2,
                name: "李"
            }, {
                id: 3,
                name: "張"
            }]
        }
    }
    // 由map 遍歷數(shù)據(jù),再由addIndex給每個數(shù)據(jù)加上一個index,并返回dom,就是我們需要的虛擬dom了
    domap = ()=> R.addIndex(R.map(this.doRender, this.state.data))
    doRender = (data,key) => {<p key={key}>{data.name}</p>}
    render() {
        return {this.domap()}
    }

demo2 fetch封裝


阮一峰ramda介紹
ramda中文網(wǎng)
ramda英文網(wǎng)

個人博客地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容