JavaScript 函數式真正的淺析

0x00 入門的導語(廢話)

最近兩年你要說函數式編程不火的話, 那是不可能的, 是人都知道函數式編程很火.爲何函數式編程會火呢, 在於它的思想, 很強大, 很強勢!尤爲是前端的redux更是在reducer上徹底使用純函數, 函數式的好處漸漸被髮掘出來, 筆者最近看了一些函數式方面的東東, 如今發出來給你們學習學習, 順便我也學習學習怎麼寫文章... :Pjavascript

經常使用的函數式庫:前端

  • ramda 設計很棒的一個庫java

  • lodash 比較經常使用的一個庫react

  • underscore 應該也不錯的一個庫編程

0x01 純函數

定義: 相同輸入必定獲得相同輸出且運行過程當中不修改,不讀取外部環境的變量的函數redux

說出來確定很差理解, 仍是要看看代碼. 就好像你不看國足比賽永遠不知道國足爲何會輸給月薪幾百塊的敘利亞.swift

// Array.slice 對於固定輸入必定是固定輸出, 且不依賴外部變量, 啥? 依賴了arr變量嗎?
// 其實這種寫法和Array.prototype.slice(arr, 0, 3); 是同樣的. 這樣就理解了,
// 你還學到一個東西 Array.slice是不會修改原數組滴!
var arr = [1,2,3,4,5];
arr.slice(0,3);

 // Array.splice 會修改xs, 因此是不純的, 因此相同的輸入不會有相同的輸出!
var xs.splice(0,3);
//=> [1,2,3]
xs.splice(0,3);
//=> [4,5]
xs.splice(0,3);
//=> []

純函數的好處: 不會去修改外部變量就不會產生線程安全問題.能夠極大的減小系統複雜程度數組

0x02 函數的柯里化

看! 代碼!安全

// 調用 doWht('我', '家裏', '飯');
let doWhat = (who, where, what) => {
  return who + '在' + where + '作' + what
}

// 柯里化後的等價效果
// 調用 doWhat('我')('家裏')('飯')
let doWhat = who => where => what => {
  return who + '在' + where + '作' + what
}

// 假設如今知道是'我'在'家', 至於作什麼是不知道的
// tmp函數就已經幫咱們保存了值, 這樣是很是靈活的.
let doWhatCurry = doWhat('我')('家裏')

上面提到的庫裏都有一個叫curry的函數會將一個普通的函數柯里化.app

0x03 函數的組合

函數組合是將函數組合在一塊兒, 生成一個新的函數

// h(g(f(x))) 這是之前調用函數的方式
var add1 = x => x + 1
var mul5 = x => x * 5
// compose會生成一個新的函數, 接收的參數所有傳給add1, 而後add1的返回值傳給mul5(注意注意!, mul5的參數個數只能有一個!!!), 而後compose生成的新的函數的返回值就是mul5的返回值.
compose(mul5, add1)(2)

函數組合很是強大, 可以經過組合的方式來生成新的函數, 這是很是爽的. 若是你運用靈活, 會極大的減小你的代碼量(若是不能減小別噴我啊), compose的實如今上面提到的三個庫中都有實現.

0x04 聲明式與命令式風格

命令式的風格讓咱們經過代碼引導機器, 讓機器一步一步完成咱們要的任務; 而聲明式則是直接告訴機器我要作啥, 更直觀.

//命令式
var persons = [...]
for (var i = 0; persons.length; ++i) {
  persons[i] = persons[i].toUppercase()
}

//聲明式
var persons = [...]
persons.map(person => person.toUppercase())

0x05 Point free風格

// 假定若是 
let map = fn => list => list.map(fn);
let add = (a, b) => a + b;

// 函數incrementAll不是point free 風格
// 由於這裏提到了numbers參數, 須要給出一個命名.
// 這樣定義函數會致使咱們須要多命名一個變量. 麻煩!
let incrementAll = (numbers) => map(add(1))(numbers);

// Point free風格的定義方法
// 假設add被柯里化過了
let incrementAll = map(add(1))

如今是推薦使用point free風格的代碼(定義函數時), 這會減小咱們沒必要要的命名. 多用這種風格哦!

0x06 容器(Functor)

容器表明了一個值, 一個任意值. 他就好像是函數式編程裏的變量,函數的一個鎧甲.可讓你的變量,函數在工程的戰場中所向披靡!

var Container = function(x) {
  this.__value = x;
}

Container.of = x => new Container(x);

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

Container.of(3).map(x => x+1).map(x => x*5)
// of用來構建容器, map用來變換容器
// Functor能夠作不少不少事情, 具體的? 往下介紹.
// Maybe就是在普通容器上新增了一個檢查空值的行爲. 
var Maybe = function(x) {
  this.__value = x;
}

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

Maybe.prototype.map = function(f) {
  return this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.__value));
}

Maybe.prototype.isNothing = function() {
  return (this.__value === null || this.__value === undefined);
}

// 例子, 若是name是空的話就會輸出空了
var functor = Maybe.of({name: ‘mrcode'})
functor
    .map(value => value.age)
    .map(String.prototype.upperCase)
    .map(value => console.log(value))

這個Maybe到底有啥用呢? 就是空值檢測, 看上面的例子, 若是不進行判空的話, 第二個map就會調用String.prototype.upperCase函數, 會拋出異常的, 怕了吧? :P, 並且, 如今不少語言,swift等都添加了相似的支持. optional

Maybe只能判空, 可是Either纔是真正的處理錯誤的容器, Either有兩個子類, Left和Right.

// Promise是經過catch方法來接收錯誤的 如:
doSomething()
    .then(async1)
    .then(async2)
    .catch(e => console.log(e));

// 徹底同樣    
var Left = function(x) {
  this.__value = x;
}
var Right = function(x) {
  this.__value = x;
}

// 徹底同樣
Left.of = function(x) {
  return new Left(x);
}
Right.of = function(x) {
  return new Right(x);
}

// 這裏不一樣!!!
Left.prototype.map = function(f) {
  return this;
}
Right.prototype.map = function(f) {
  return Right.of(f(this.__value));
}

// 應用:
var getAge = user => user.age ? Right.of(user.age) : Left.of("ERROR!")
getAge({name: 'stark', age: '21'}).map(age => 'Age is ' + age);
//=> Right('Age is 21')

getAge({name: 'stark'}).map(age => 'Age is ' + age);
//=> Left('ERROR!')

Left會跳過全部執行過程, 直達結果, 這就好像Right是流程圖裏一個又一個指向下一個任務的箭頭, 而Left直接指向告終果, 是錯誤的結果.

0x07 IO

誒, 函數式編程裏, 涉及到IO老是讓人尷尬的, 藍瘦的很..幸虧, 有一種叫作IO的東西專門處理IO這種東西(別嫌繞哈), 看代碼,

// 沒毛病
var IO = function(f) {
    this.__value = f;
}

// ??? 看不懂, 待會解釋..
IO.of = x => new IO(_ => x);

// ??? 這是啥子鬼????
IO.prototype.map = function(f) {
    return new IO(compose(f, this.__value))
};

權威解答: 這裏的IO裏存的是一個函數, 包裹了外部環境變量的函數, 咱們傳入了一個函數, 這個函數裏包含了實際的值,會進行IO操做. 咱們把不純的IO操做放到了這個函數裏, 整體上看, 咱們的IO對象, 是不會執行這些不純的操做的. 它依然是純的, 由於IO操做壓根就沒執行內部包含的函數, 這個函數是外部調用者去執行的. 也就是說, 不純的操做是外部的人乾的, 和咱們的IO對象一丟丟關係都木有!(幹得漂亮!) 看一個例子.

var io_document = new IO(_ => window.document);
io_document.map(function(doc){ return doc.title });
// 獲得IO(documen.title)

科普: 這裏你沒有獲得document.title, 你獲得的僅僅是一個會返回document.title的一個函數, 這個函數是不純的, 可是執行不是由上面的代碼執行的, 鍋在調用函數的人身上! 上面的代碼依然是'純'的!

0x08 Monad

看這個部分的時候建議看一下IO的實現, 好好理解一下, 我知道有點燒腦, 可是看一下沒壞處!玩過Promise的都知道, Promise.then傳進去的函數能夠返回一個新的Promise. Promise就是Monad.

0x09 函數式編程的應用

react中的純組件

// 固定的輸入獲得固定的輸出 純組件極大的增長了react的靈活程度
// app 的狀態交給一些狀態機管理 好比redux
var Text = props => (
    <div style={props.style}>{props.text}</div>
)

redux中的reducer

// 輸入當前狀態和action, 輸出nowState
reducer(currentState, action) => newState

0x10 總結一下

確實是這樣, 不總結的話就不像是一篇文章了, 仍是總結下吧:

  • 純函數的概念以及函數柯里化和函數的組合

  • 容器概念, Container和Maybe, Either的派生Left,Right, IO做用.

  • 函數式編程的應用

參考文章

JavaScript函數式編程3

好了, 再見 古德曼~

相關文章
相關標籤/搜索