摘要:而在函數式編程中方法是獨立于數據的,我們可以把上面以函數式的方式在寫一遍你肯定會說,你是在逗我。對比兩個生成新函數的過程,沒有柯里化的相對而言就有一點啰嗦了。
我們都知道單一職責原則,其實面向對象的SOLID中的S(SRP, Single responsibility principle)。在函數式當中每一個函數就是一個單元,同樣應該只做一件事。但是現實世界總是復雜的,當把現實世界映射到編程時,單一的函數就沒有太大的意義。這個時候就需要函數組合和柯里化了。
鏈式調用如果用過jQuery的都曉得啥是鏈式調用,比如$(".post").eq(1).attr("data-test", "test").javascript原生的一些字符串和數組的方法也能寫出鏈式調用的風格:
"Hello, world!".split("").reverse().join("") // "!dlrow ,olleH"
首先鏈式調用是基于對象的,上面的一個一個方法split, reverse, join如果脫離的前面的對象"Hello, world!"是玩不起來的。
而在函數式編程中方法是獨立于數據的,我們可以把上面以函數式的方式在寫一遍:
const split = (tag, xs) => xs.split(tag) const reverse = xs => xs.reverse() const join = (tag, xs) => xs.join(tag) join("",reverse(split("","Hello, world!"))) // "!dlrow ,olleH"
你肯定會說,你是在逗我。這比鏈式調用好在哪兒了?這里還是依賴于數據的啊,沒有傳遞`"Hello, world!",你這一串一串的函數組合也轉不起來啊。這里唯一的好處也就是那幾個多帶帶的方法可以復用了。莫慌,后面還有那么多內容我怎么也會給你優化(忽悠)好的。再進行改造前,我們先介紹兩個概念,部分應用和柯里化。
部分應用部分應用是一種處理函數參數的流程,他會接收部分參數,然后返回一個函數接收更少的參數。這個就是部分應用。我們用bind來實現一把:
const addThreeArg = (x, y, z) => x + y + z; const addTwoArg = addThreeNumber.bind(null, 1) const addOneArg = addThreeNumber.bind(null, 1, 2) addTwoArg(2, 3) // 6 addOneArg(7) // 10
上面利用bind生成了另外兩個函數,分別接受剩下的參數,這就是部分應用。當然你也可以通過其他方式實現。
部分應用存在的問題部分應用主要的問題在于,它返回的函數類型無法直接推斷。正如前面所說,部分應用返回一個函數接收更少的參數,而沒有規定返回的參數具體是多少個。這也就是一些隱式的東西,你需要去查看代碼。才知道返回的函數接收多少個參數。
柯里化柯里化定義:你可以調一個函數,但是不一次將所有參數傳給它。這個函數會返回一個函數去接收下一個參數。
const add = x => y => x + y const plusOne = add(1) plusOne(10) // 11
柯里化的函數返回一個只接收一個參數的函數,返回的函數類型可以預測。
當然在實際開發中,有很多的函數都不是柯里化的,我們可以使用一些工具函數來轉化:
const curry = (fn) => { // fn可以是任何參數的函數 const arity = fn.length; return function $curry(...args) { if (args.length < arity) { return $curry.bind(null, ...args); } return fn.call(null, ...args); }; };
也可以用開源庫Ramda里提供的curry方法。
哦,柯里化。有什么用呢?舉個例子
const currySplit = curry((tag, xs) => xs.split(tag)) const split = (tag, xs) => xs.split(tag) // 我現在需要一個函數去split "," const splitComma = currySplit(",") //by curry const splitComma = string => split(",", string)
可以看到柯里化的函數生成新函數時,和數據完全沒有關系。對比兩個生成新函數的過程,沒有柯里化的相對而言就有一點啰嗦了。
函數組合先給代碼:
const compose = (...fns) => (...args) => fns.reduceRight((res, fn) => [fn.call(null, ...res)], args)[0];
其實compose做的事情一共兩件:
接收一組函數,返回一個函數,不立即執行函數
組合函數,將傳遞給他的函數從左到右組合。
可能有同學對上面的reduceRight不是很熟悉,我給個2元和3元的例子:
const compose = (f, g) => (...args) => f(g(...args)) const compose3 = (f, g, z) => (...args) => f(g(z(...args)))
函數調用是從左到右,數據流也是一樣的從左到右。當然你可以定義從右到左的,不過從語義上來說就不那么表意了。
好,現在讓我們來優化一下最開始的例子:
const split = curry((tag, xs) => xs.split(tag)) const reverse = xs => xs.reverse() const join = curry((tag, xs) => xs.join(tag)) const reverseWords = compose(join(""), reverse, split("")) reverseWords("Hello,world!");
是不是簡潔易于理解多了。這里的reverseWords也是我們之前講過的Pointfree的代碼風格。不依賴數據和外部狀態,就是組合在一起的一個函數。
Pointfree我在上一篇介紹過JS函數式編程 - 概念,也闡述了其優缺點,有興趣的小伙伴可以看看。
函數組合的結合律先回顧一下小學知識加法結合律:a+(b+c)=(a+b)+c。我就不解釋了,你們應該能理解。
回過來看函數組合其實也存在結合律的:
compose(f, compose(g, h)) === compose(compose(f, g), h);
這個對于我們編程有一個好處,我們的函數組合可以隨意組合并且緩存:
const split = curry((tag, xs) => xs.split(tag)) const reverse = xs => xs.reverse() const join = curry((tag, xs) => xs.join(tag)) const getReverseArray = compose(reverse, split("")) const reverseWords = compose(join(""), getReverseArray) reverseWords("Hello,world!");
腦圖補充:
OK,下一篇介紹一下范疇輪,和函子。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/98329.html
摘要:函數式編程,一看這個詞,簡直就是學院派的典范。所以這期周刊,我們就重點引入的函數式編程,淺入淺出,一窺函數式編程的思想,可能讓你對編程語言的理解更加融會貫通一些。但從根本上來說,函數式編程就是關于如使用通用的可復用函數進行組合編程。 showImg(https://segmentfault.com/img/bVGQuc); 函數式編程(Functional Programming),一...
摘要:組合的概念是非常直觀的,并不是函數式編程獨有的,在我們生活中或者前端開發中處處可見。其實我們函數式編程里面的組合也是類似,函數組合就是一種將已被分解的簡單任務組織成復雜的整體過程。在函數式編程的世界中,有這樣一種很流行的編程風格。 JavaScript函數式編程,真香之認識函數式編程(一) 該系列文章不是針對前端新手,需要有一定的編程經驗,而且了解 JavaScript 里面作用域,閉...
摘要:所以下面介紹一些函數式編程的知識和概念。函數式編程的一個明顯的好處就是這種聲明式的代碼,對于無副作用的純函數,我們完全可以不考慮函數內部是如何實現的,專注于編寫業務代碼。 原文鏈接 引言 說到函數式編程,大家可能第一印象都是學院派的那些晦澀難懂的代碼,充滿了一大堆抽象的不知所云的符號,似乎只有大學里的計算機教授才會使用這些東西。在曾經的某個時代可能確實如此,但是近年來隨著技術的發展,函...
摘要:所以下面介紹一些函數式編程的知識和概念。函數式編程的一個明顯的好處就是這種聲明式的代碼,對于無副作用的純函數,我們完全可以不考慮函數內部是如何實現的,專注于編寫業務代碼。我會在下一篇文章中介紹函數式編程的更加高階一些的知識,例如等等概念。 一、引言 說到函數式編程,大家可能第一印象都是學院派的那些晦澀難懂的代碼,充滿了一大堆抽象的不知所云的符號,似乎只有大學里的計算機教授才會使用這些東...
摘要:作為函數式編程語言,帶來了很多語言上的有趣特性,比如柯里化和反柯里化。在一些函數式編程語言中,會定義一個特殊的占位變量。個人理解不知道對不對延遲執行柯里化的另一個應用場景是延遲執行。不斷的柯里化,累積傳入的參數,最后執行。作為函數式編程語言,JS帶來了很多語言上的有趣特性,比如柯里化和反柯里化。 這里可以對照另外一篇介紹 JS 反柯里化 的文章一起看~ 1. 簡介 柯里化(Currying)...
閱讀 2731·2023-04-26 02:28
閱讀 2565·2021-09-27 13:36
閱讀 3134·2021-09-03 10:29
閱讀 2762·2021-08-26 14:14
閱讀 2111·2019-08-30 15:56
閱讀 842·2019-08-29 13:46
閱讀 2616·2019-08-29 13:15
閱讀 461·2019-08-29 11:29