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