首頁 >web前端 >js教程 >javascript的函數組合與柯里化的詳解(附範例)

javascript的函數組合與柯里化的詳解(附範例)

不言
不言轉載
2018-10-13 14:42:471920瀏覽

這篇文章帶給大家的內容是關於javascript的函數組合與柯里化的詳解(附範例),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

我們都知道單一職責原則,其實物件導向的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 <p>也可以用開源函式庫Ramda裡提供的curry方法。 </p><h3>哦,柯里化。有什麼用呢? </h3><p>舉例</p><pre class="brush:php;toolbar:false">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做的事情一共兩件:

  1. 接收一組函數,傳回一個函數,不立即執行函數

  2. 組合函數,將傳遞給他的函數從左到右組合。

可能有同學對上面的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!');

腦圖補充:

javascript的函數組合與柯里化的詳解(附範例)


#

以上是javascript的函數組合與柯里化的詳解(附範例)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除