摘要:作為函數式編程語言,帶來了很多語言上的有趣特性,比如柯里化和反柯里化。個人理解不知道對不對延遲執行柯里化的另一個應用場景是延遲執行。不斷的柯里化,累積傳入的參數,最后執行。
作為函數式編程語言,JS帶來了很多語言上的有趣特性,比如柯里化和反柯里化。
這里可以對照另外一篇介紹 JS 反柯里化 的文章一起看~
1. 簡介柯里化(Currying),又稱部分求值(Partial Evaluation),是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,并且返回接受余下的參數而且返回結果的新函數的技術。
核心思想是把多參數傳入的函數拆成單參數(或部分)函數,內部再返回調用下一個單參數(或部分)函數,依次處理剩余的參數。
按照Stoyan Stefanov --《JavaScript Pattern》作者 的說法,所謂“柯里化”就是使函數理解并處理部分應用
柯里化有3個常見作用:
參數復用
提前返回
延遲計算/運行
talk is cheap,看看怎么實現吧~
2. 實現 2.1 通用實現一個通用實現:
function currying(fn, ...rest1) { return function(...rest2) { return fn.apply(null, rest1.concat(rest2)) } }
注意這里concat接受非數組元素參數將被當做調用者的一個元素傳入
用它將一個sayHello函數柯里化試試:
function sayHello(name, age, fruit) { console.log(console.log(`我叫 ${name},我 ${age} 歲了, 我喜歡吃 ${fruit}`)) } const curryingShowMsg1 = currying(sayHello, "小明") curryingShowMsg1(22, "蘋果") // 我叫 小明,我 22 歲了, 我喜歡吃 蘋果 const curryingShowMsg2 = currying(sayHello, "小衰", 20) curryingShowMsg2("西瓜") // 我叫 小衰,我 20 歲了, 我喜歡吃 西瓜
嘻嘻,感覺還行~
2.2 高階柯里化函數以上柯里化函數已經能解決一般需求了,但是如果要多層的柯里化總不能不斷地進行currying函數的嵌套吧,我們希望經過柯里化之后的函數每次只傳遞一個或者多個參數,那該怎么做呢:
function curryingHelper(fn, len) { const length = len || fn.length // 第一遍運行length是函數fn一共需要的參數個數,以后是剩余所需要的參數個數 return function(...rest) { return rest.length >= length // 檢查是否傳入了fn所需足夠的參數 ? fn.apply(this, rest) : curryingHelper(currying.apply(this, [fn].concat(rest)), length - rest.length) // 在通用currying函數基礎上 } } function sayHello(name, age, fruit) { console.log(`我叫 ${name},我 ${age} 歲了, 我喜歡吃 ${fruit}`) } const betterShowMsg = curryingHelper(sayHello) betterShowMsg("小衰", 20, "西瓜") // 我叫 小衰,我 20 歲了, 我喜歡吃 西瓜 betterShowMsg("小豬")(25, "南瓜") // 我叫 小豬,我 25 歲了, 我喜歡吃 南瓜 betterShowMsg("小明", 22)("倭瓜") // 我叫 小明,我 22 歲了, 我喜歡吃 倭瓜 betterShowMsg("小拽")(28)("冬瓜") // 我叫 小拽,我 28 歲了, 我喜歡吃 冬瓜
如此實現一個高階的柯里化函數,使得柯里化一個函數的時候可以不用嵌套的currying,當然是因為把嵌套的地方放到了curryingHelper里面進行了...-。-
2.3 瘋狂柯里化函數盡管柯里化函數已經很牛了,但是它也讓你必須花費點小心思在你所定義函數的參數順序上。在一些函數式編程語言中,會定義一個特殊的“占位變量”。通常會指定下劃線來干這事,如果作為一個函數的參數被傳入,就表明這個是可以“跳過的”,是尚待指定的參數。比如:
var sendAjax = function (url, data, options) { /* ... */ } var sendPost = function (url, data) { // 當然可以這樣 return sendAjax(url, data, { type: "POST", contentType: "application/json" }) } // 也可以使用下劃線來指定未確定的參數 var sendPost = sendAjax( _ , _ , { type: "POST", contentType: "application/json" })
JS不具備這樣的原生支持,可以使用一個全局占位符變量const _ = { }并且通過===來判斷是否是占位符,當然你如果使用了lodash的話可以使用別的符號代替。那么可以這樣改造柯里化函數:
const _ = {} function crazyCurryingHelper(fn, length, args, holes) { length = length || fn.length // 第一遍是fn所需的參數個數,以后是 args = args || [] holes = holes || [] return function(...rest) { let _args = args.slice(), _holes = holes.slice(), argLength = _args.length, // 存儲接收到的args和holes的長度 holeLength = _holes.length, arg, i = 0 for (; i < rest.length; i++) { arg = rest[i] if (arg === _ && holeLength) { holeLength-- // 循環_holes的位置 _holes.push(_holes.shift()) // _holes最后一個移到第一個 } else if (arg === _) { _holes.push(argLength + i) // 存儲_hole就是_的位置 } else if (holeLength) { // 是否還有沒有填補的hole holeLength-- _args.splice(_holes.shift(), 0, arg) // 在參數列表指定hole的地方插入當前參數 } else { _args.push(arg) // 不需要填補hole,直接添加到參數列表里面 } } return _args.length >= length // 遞歸的進行柯里化 ? fn.apply(this, _args) : crazyCurryingHelper.call(this, fn, length, _args, _holes) } } function sayHello(name, age, fruit) { console.log(`我叫 ${name},我 ${age} 歲了, 我喜歡吃 ${fruit}`) } const betterShowMsg = crazyCurryingHelper(sayHello) betterShowMsg(_, 20)("小衰", _, "西瓜") // 我叫 小衰,我 20 歲了, 我喜歡吃 西瓜 betterShowMsg(_, _, "南瓜")("小豬")(25) // 我叫 小豬,我 25 歲了, 我喜歡吃 南瓜 betterShowMsg("小明")(_, 22)(_, _, "倭瓜") // 我叫 小明,我 22 歲了, 我喜歡吃 倭瓜 betterShowMsg("小拽")(28)("冬瓜") // 我叫 小拽,我 28 歲了, 我喜歡吃 冬瓜
牛B閃閃
3. 柯里化的常見用法 3.1 參數復用通過柯里化方法,緩存參數到閉包內部參數,然后在函數內部將緩存的參數與傳入的參數組合后apply/bind/call給函數執行,來實現參數的復用,降低適用范圍,提高適用性。
參看以下栗子,官員無論添加后續老婆,都能和合法老婆組合,通過柯里化方法,getWife方法就無需添加多余的合法老婆...
var currying = function(fn) { var args = [].slice.call(arguments, 1) // fn 指官員消化老婆的手段,args 指的是那個合法老婆 return function(...rest) { var newArgs = args.concat(...rest) // 已經有的老婆和新搞定的老婆們合成一體,方便控制 return fn.apply(null, newArgs) // 這些老婆們用 fn 這個手段消化利用,完成韋小寶前輩的壯舉并返回 } } var getWife = currying(function() { console.log([...arguments].join(";")) // allwife 就是所有的老婆的,包括暗渡陳倉進來的老婆 }, "合法老婆") getWife("老婆1", "老婆2", "老婆3") // 合法老婆;老婆1;老婆2;老婆3 getWife("超越韋小寶的老婆") // 合法老婆;超越韋小寶的老婆 getWife("超級老婆") // 合法老婆;超級老婆3.2 提高適用性
通用函數解決了兼容性問題,但同時也會再來,使用的不便利性,不同的應用場景往,要傳遞很多參數,以達到解決特定問題的目的。有時候應用中,同一種規則可能會反復使用,這就可能會造成代碼的重復性。
// 未柯里化前 function square(i) { return i * i; } function dubble(i) { return i * 2; } function map(handler, list) { return list.map(handler); } map(square, [1, 2, 3, 4, 5]); // 數組的每一項平方 map(square, [6, 7, 8, 9, 10]); map(dubble, [1, 2, 3, 4, 5]); // 數組的每一項加倍 map(dubble, [6, 7, 8, 9, 10]);
同一規則重復使用,帶來代碼的重復性,因此可以使用上面的通用柯里化實現改造一下:
// 柯里化后 function square(i) { return i * i; } function dubble(i) { return i * 2; } function map(handler, ...list) { return list.map(handler); } var mapSQ = currying(map, square); mapSQ([1, 2, 3, 4, 5]); mapSQ([6, 7, 8, 9, 10]); var mapDB = currying(map, dubble); mapDB([1, 2, 3, 4, 5]); mapDB([6, 7, 8, 9, 10]);
可以看到這里柯里化方法的使用和偏函數比較類似,順便回顧一下偏函數~
偏函數是創建一個調用另外一個部分(參數或變量已預制的函數)的函數,函數可以根據傳入的參數來生成一個真正執行的函數。比如:
const isType = function(type) { return function(obj) { return Object.prototype.toString.call(obj) === `[object ${type}]` } } const isString = isType("String") const isFunction = isType("Function")
這樣就用偏函數快速創建了一組判斷對象類型的方法~
偏函數固定了函數的某個部分,通過傳入的參數或者方法返回一個新的函數來接受剩余的參數,數量可能是一個也可能是多個
柯里化是把一個有n個參數的函數變成n個只有1個參數的函數,例如:add = (x, y, z) => x + y + z→curryAdd = x => y => z => x + y + z
當偏函數接受一個參數并且返回了一個只接受一個參數的函數,與兩個接受一個參數的函數curry()()的柯里化函數,這時候兩個概念類似。(個人理解不知道對不對)
柯里化的另一個應用場景是延遲執行。不斷的柯里化,累積傳入的參數,最后執行。例如累加:
const curryAdd = function(...rest) { const _args = rest return function cb(...rest) { if (rest.length === 0) { return _args.reduce((sum, single) => sum += single) } else { _args.push(...rest) return cb } } }() // 為了保存添加的數,這里要返回一個閉包 curryAdd(1) curryAdd(2) curryAdd(3) curryAdd(4) curryAdd() // 最后計算輸出:10
更通用的寫法,將處理函數提取出來:
const curry = function(fn) { const _args = [] return function cb(...rest) { if (rest.length === 0) { return fn.apply(this, _args) } _args.push(...rest) return cb } } const curryAdd = curry((...T) => T.reduce((sum, single) => sum += single) ) curryAdd(1) curryAdd(2) curryAdd(3) curryAdd(4) curryAdd() // 最后計算輸出:104. Function.prototype.bind 方法也是柯里化應用
與 call/apply 方法直接執行不同,bind 方法將第一個參數設置為函數執行的上下文,其他參數依次傳遞給調用方法(函數的主體本身不執行,可以看成是延遲執行),并動態創建返回一個新的函數, 這符合柯里化特點。
var foo = {x: 888}; var bar = function () { console.log(this.x); }.bind(foo); // 綁定 bar(); // 888
下面是一個 bind 函數的模擬,testBind 創建并返回新的函數,在新的函數中將真正要執行業務的函數綁定到實參傳入的上下文,延遲執行了。
Function.prototype.testBind = function(scope) { return () => this.apply(scope) } var foo = { x: 888 } var bar = function() { console.log(this.x) }.testBind(foo) // 綁定 bar() // 888
網上的帖子大多深淺不一,甚至有些前后矛盾,在下的文章都是學習過程中的總結,如果發現錯誤,歡迎留言指出~
參考:
JS高級程序設計
JS中的柯里化(currying)
前端開發者進階之函數柯里化Currying
淺析 JavaScript 中的 函數 currying 柯里化
掌握JavaScript函數的柯里化
函數式JavaScript(4):函數柯里化
PS:歡迎大家關注我的公眾號【前端下午茶】,一起加油吧~
另外可以加入「前端下午茶交流群」微信群,長按識別下面二維碼即可加我好友,備注加群,我拉你入群~
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/90778.html
摘要:笑中自動柯里化的精巧實現柯里化是函數式編程中很重要的一環,很多函數式語言都會默認將函數自動柯里化。 什么是柯里化? 在計算機科學中,柯里化(Currying)是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,并且返回接受余下的參數且返回結果的新函數的技術。這個技術由 Christopher Strachey 以邏輯學家 Haskell Curry 命名的,盡管...
摘要:今天了解到一個新名詞柯里化,研究一番后總結如下一柯里化定義把接受多個參數的函數變換成接受一個單一參數最初函數的第一個參數的函數,并且返回接受余下的參數且返回結果的新函數的技術。如果使用反柯里化,則可以這樣寫震驚某前端只會,竟月入百萬。。。 今天了解到一個新名詞:柯里化,研究一番后總結如下: 一· 柯里化 定義 把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,并...
摘要:作為函數式編程語言,帶來了很多語言上的有趣特性,比如柯里化和反柯里化。而反柯里化,從字面講,意義和用法跟函數柯里化相比正好相反,擴大適用范圍,創建一個應用范圍更廣的函數。作為函數式編程語言,JS帶來了很多語言上的有趣特性,比如柯里化和反柯里化。 可以對照另外一篇介紹 JS 柯里化 的文章一起看~ 1. 簡介 柯里化,是固定部分參數,返回一個接受剩余參數的函數,也稱為部分計算函數,目的是為了縮...
摘要:何為化柯里化化來源與數學家的名字編程語言也是以他的名字命名。因此柯里化的過程是逐步傳參,逐步縮小函數的適用范圍,逐步求解的過程。當在多次調用同一個函數,并且傳遞的參數絕大多數是相同的,那么該函數可能是一個很好的柯里化候選。 何為Curry化/柯里化? curry化來源與數學家 Haskell Curry的名字 (編程語言 Haskell也是以他的名字命名)。 柯里化通常也稱部分求值,其...
摘要:原文鏈接和都支持函數的柯里化函數的柯里化還與的函數編程有很大的聯系如果你感興趣的話可以在這些方面多下功夫了解相信收獲一定很多看本篇文章需要知道的一些知識點函數部分的閉包高階函數不完全函數文章后面有對這些知識的簡單解釋大家可以看看什么是柯里化 原文鏈接 Haskell和scala都支持函數的柯里化,JavaScript函數的柯里化還與JavaScript的函數編程有很大的聯系,如果你感興...
閱讀 1884·2021-11-12 10:36
閱讀 2316·2021-09-01 10:29
閱讀 2345·2019-08-30 15:56
閱讀 1021·2019-08-30 12:56
閱讀 2352·2019-08-26 13:58
閱讀 2270·2019-08-23 18:38
閱讀 1490·2019-08-23 18:32
閱讀 2108·2019-08-23 16:53