JS函数式编程【译】5.2 函子 (Functors)
函子(Functors)
態(tài)射是類型之間的映射;函子是范疇之間的映射。可以認(rèn)為函子是這樣一個函數(shù),它從一個容器中取出值, 并將其加工,然后放到一個新的容器中。這個函數(shù)的第一個輸入的參數(shù)是類型的態(tài)射,第二個輸入的參數(shù)是容器。
函子的函數(shù)簽名是這個樣子// myFunctor :: (a -> b) -> f a -> f b
意思是“給我一個傳入a返回b的函數(shù)和一個包含a(一個或多個)的容器,我會返回一個包含b(一個或多個)的容器”
創(chuàng)建函子
要知道我們已經(jīng)有了一個函子:map(),它攫取包含一些值的容器(數(shù)組),然后把一個函數(shù)作用于它。
[1, 4, 9].map(Math.sqrt); // Returns: [1, 2, 3] 然而我們要把它寫成一個全局函數(shù),而不是數(shù)組對象的方法。這樣我們后面就可以寫出簡潔、安全的代碼。
// map :: (a -> b) -> [a] -> [b]
var map = function(f, a) {return arr(a).map(func(f));
} 這個例子看起來像是個故意弄的封裝,因為我們只是把map()函數(shù)換了個形式。但這有它的目的。 它為映射其它類型提供了一個模板。
// strmap :: (str -> str) -> str -> str
var strmap = function(f, s) {return str(s).split('').map(func(f)).join('');
}
數(shù)組和函子
數(shù)組是函數(shù)式JavaScript使用數(shù)據(jù)的最好的方式。
是否有一種簡單的方法來創(chuàng)建已經(jīng)分配了態(tài)射的函子?有,它叫做arrayOf。 當(dāng)你傳入一個以整數(shù)為參數(shù)、返回數(shù)組的態(tài)射時,你會得到一個以整數(shù)數(shù)組為參數(shù)返回數(shù)組的數(shù)組的態(tài)射。
它自己本身不是函子,但是它讓我們能夠用態(tài)射建立函子。
// arrayOf :: (a -> b) -> ([a] -> [b])
var arrayOf = function(f) {return function(a) {return map(func(f), arr(a));}
} 下面是如何用態(tài)射創(chuàng)建函子
var plusplusall = arrayOf(plusplus); // plusplus是函子
console.log( plusplusall([1,2,3]) ); // 返回[2,3,4]
console.log( plusplusall([1,'2',3]) ); // 拋出錯誤
函數(shù)組合,重訪(revisited)
函數(shù)也是一種我們能夠用函子來創(chuàng)建的原始類型,這個函子叫做“fcompose”。我們對函子是這樣定義的: 它從容器中取一個值,并對其應(yīng)用一個函數(shù)。如果這個容器是一個函數(shù),我們只需要調(diào)用它并獲取里面的值。
我們已經(jīng)知道了什么是函數(shù)組合,不過讓我們來看看在范疇論驅(qū)動的環(huán)境里它們能做些什么。
函數(shù)組合就是結(jié)合(associative,中學(xué)數(shù)學(xué)中學(xué)到的“結(jié)合律”中的“結(jié)合”)。如果你的高中代數(shù)老師也像我這樣的話那她只告訴了你函數(shù)組合的定律有什么,而沒有沒教你用它能做些什么。在實踐中,組合就是結(jié)合律所能夠做的。
(a × b) × c = a × (b × c)(f?g)?h = f?(g?h)
f?g ≠ g?f
我們可以任意進行內(nèi)部組合,無所謂怎樣分組。交換律也沒有什么可迷惑的。f?g 不總等于 g?f。比如說,一個句子的第一個單詞被反轉(zhuǎn)并不等同于一個被反轉(zhuǎn)的句子的第一個單詞。
總的來說意思就是哪個函數(shù)以什么樣的順序被執(zhí)行是無所謂的,只要每個函數(shù)的輸入來源于上一個函數(shù)的輸出。不過,等等,如果右邊的函數(shù)依賴于左邊的函數(shù),不就是只有一個固定的求值順序嗎?從左到右?是的,如果把它封裝起來,我們就可以按照我們感覺合適的方式來控制它。這就使得在JavaScript中可以實現(xiàn)惰性求值。
(a × b) × c = a × (b × c)(f?g)?h = f?(g?h)
我們來重寫函數(shù)組合,不作為函數(shù)原型的擴展,而是作為一個單獨的函數(shù),這樣我們就可以的到更多的功能。基本的形式是這樣的:
var fcompose = function(f, g) {return function() {return f.call(this, g.apply(this, arguments));};
}; 不過我們還得讓它能接受任意數(shù)量的輸入。
var fcompose = function() {// 首先確保所有的參數(shù)都是函數(shù)var funcs = arrayOf(func)(arguments); //譯注:這句有問題,見下面注釋// 返回一個作用于所有函數(shù)的函數(shù)return function() {var argsOfFuncs = arguments;for (var i = funcs.length; i > 0; i -= 1) {argsOfFuncs = [funcs[i].apply(this, args)];}return args[0];};
};// 例:
var f = fcompose(negate, square, mult2, add1);
f(2); // 返回: -36 給原著勘誤:如果你copy上面的代碼執(zhí)行的話現(xiàn)在肯定看到報錯了,上面這段代碼里的錯誤還真不少……
首先會得到一個錯誤:“Uncaught TypeError: Error: Array expected, something else given.”。 哪個數(shù)組沒通過類型驗證呢?是fcompose里的arguments。我在最新版本的chrome和火狐里得到arguments的字符串是[object Arguments], 而且arguments并沒有繼承Array,也就沒有map之類的方法,所以這里需要先把arguments轉(zhuǎn)換成數(shù)組,把fcompose函數(shù)體第一句改成這樣就行:
var funcs = arrayOf(func)(Array.prototype.slice.call(arguments));
然后第二個錯誤,低級錯誤,argsOfFuncs和args是一個東西,統(tǒng)一成一個變量名就行了。比如說把argsOfFuncs都改成args吧。 順便說一下這里的意思,首先把初始參數(shù)賦給args,然后遍歷組合函數(shù)的數(shù)組,每執(zhí)行一個函數(shù)就把返回值賦給args, 這樣下一個函數(shù)就能把上一個函數(shù)的執(zhí)行結(jié)果作為輸入?yún)?shù)了。注意每次的返回值都放到了數(shù)組里,是為了符合apply的參數(shù)形式, 而最后返回時只要取args里的第一個(也是唯一一個)值就行了。
第三個錯誤,還是低級錯誤,遍歷funcs的時候計數(shù)寫成了length到1,而實際上我們需要length-1到0。 順便說下為什么計數(shù)要從大到小呢?因為組合的函數(shù)要從右往左執(zhí)行。
最后,上正確的代碼:
var fcompose = function() {var funcs = arrayOf(func)(Array.prototype.slice.call(arguments));return function() {var args = arguments;for (var i = funcs.length-1; i >= 0; i -= 1) {args = [funcs[i].apply(this, args)];}return args[0];};
}; 現(xiàn)在我們封裝好了這些函數(shù)并可以控制它們了。我們重寫了組合函數(shù)使得每一個函數(shù)接受另一個函數(shù)作為輸入, 存儲起來,并同樣返回一個對象。這里并不是接受一個數(shù)組作為輸入處理它,而是對每一個操作返回一個新的數(shù)組, 我們可以在源頭上讓每一個元素接受一個數(shù)組,把所有操作合到一起執(zhí)行(所有map、filter等等組合到一起), 最終把結(jié)果存到一個新數(shù)組里。這就是通過函數(shù)組合實現(xiàn)的惰性求值。這里我們沒有理由重新造輪子, 許多庫對于這個概念都有很好的實現(xiàn),包括Lazy.js、Bacon.js以及wu.js等庫。
利用這一不同模式的結(jié)果,我們可以做更多事情:異步迭代、異步事件處理、惰性求值甚至自動并行。
自動并行?在計算機科學(xué)界有一個詞叫做:IMPOSSIBLE。但是這真的不可能嗎? 摩爾定律的下一個飛躍沒準(zhǔn)是一個能夠?qū)⑽覀兊拇a并行化的編譯器,函數(shù)組合能做到嗎? 不,這行不通。JavaScript引擎實現(xiàn)并行化并不是自動的,而是依靠精心設(shè)計的代碼。 函數(shù)組合只是提供了切分成并行進程的機會。但是它本身已經(jīng)足夠酷了。 下一節(jié) 單子(Monads) ? Functional Programming in Javascript 主目錄第五章 范疇論轉(zhuǎn)載于:https://www.cnblogs.com/tolg/p/5258029.html
總結(jié)
以上是生活随笔為你收集整理的JS函数式编程【译】5.2 函子 (Functors)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux 串口minicom配置使用
- 下一篇: 哒哒哒哒哒哒是什么歌呢?