js函數式淺析
0x00 入門的導語(廢話)
最近兩年你要說函數式程式設計不火的話, 那是不可能的, 是人都知道函數式程式設計很火.為什麼函數式程式設計會火呢, 在于它的思想, 很強大,
很強勢!尤其是前端的redux更是在reducer上完全使用純函數, 函數式的好處漸漸被發掘出來, 筆者最近看了一些函數式方面的東東,
現在發出來給大家學習學習, 順便我也學習學習怎麼寫文章... :p
常用的函數式庫:
ramda 設計很棒的一個庫
lodash 比較常用的一個庫
underscore 應該也不錯的一個庫
0x01 純函數
定義: 相同輸入一定得到相同輸出且運作過程中不修改,不讀取外部環境的變量的函數
說出來肯定不好了解, 還是要看看代碼. 就好像你不看國足比賽永遠不知道國足為什麼會輸給月薪幾百塊的叙利亞.
// 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]
//=> []
純函數的好處: 不會去修改外部變量就不會産生線程安全問題.可以極大的減少系統複雜程度
0x02 函數的柯裡化
看! 代碼!
// 調用 dowht('我', '家裡', '飯');
let dowhat = (who, where, what) => {
return who + '在' + where + '做' + what
}
// 柯裡化後的等價效果
// 調用 dowhat('我')('家裡')('飯')
let dowhat = who => where => what => {
// 假設現在知道是'我'在'家', 至于做什麼是不知道的
// tmp函數就已經幫我們儲存了值, 這樣是非常靈活的.
let dowhatcurry = dowhat('我')('家裡')
上面提到的庫裡都有一個叫curry的函數會将一個普通的函數柯裡化.
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()
//聲明式
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) {
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) {
var right = function(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作用.
函數式程式設計的應用
作者:mrcode
來源:51cto