目錄
1. 高階函數的坑
2. 函數柯里化(curry)
3. 函數柯里化進一步思考
4. 函数柯里化的递归调用
5. 函数组合(compose)
首頁 web前端 js教程 詳細介紹JavaScript函數柯里化的一些思考

詳細介紹JavaScript函數柯里化的一些思考

Mar 08, 2017 pm 02:20 PM

1. 高階函數的坑

在學習柯里化之前,我們先來看看下面一段程式碼:

var f1 = function(x){
    return f(x);
};
f1(x);
登入後複製

很多同學都能看出來,這些寫是非常愚蠢的,因為函數f1f是等效的,我們直接令var f1 = f;就行了,完全沒有必要包裹那麼一層。

但是,下面一段程式碼就未必能夠看得出問題來了:

var getServerStuff = function(callback){
  return ajaxCall(function(json){
    return callback(json);
  });
};
登入後複製

這是我摘自《JS函數式程式設計指南》中的一段程式碼,實際上,利用上面的規則,我們可以得到callback與函數

function(json){return callback(json);};
登入後複製

是等價的,所以函數可以化簡為:

var getServerStuff = function(callback){
  return ajaxCall(callback);
};
登入後複製

繼續化簡:

var getServerStuff = ajaxCall;
登入後複製

如此一來,我們發現那麼長一段程式都白寫了。

函數既可以當參數,又可以當回傳值,是高階函數的重要特性,但是稍不留神就容易踩到坑裡。

2. 函數柯里化(curry)

言歸正傳,什麼是函數柯里化?函數柯里化(curry)就是只傳遞給函數一部分參數來呼叫它,讓它回傳一個函數去處理剩下的參數。聽得很繞口,其實很簡單,其實就是將函數的變數分開來呼叫:f(x,y,z) -> f(x)(y)(z)

對於最開始的例子,按照如下實現,要傳入兩個參數,f1呼叫方式是f1(f,x)

var f1 = function(f,x){
    return f(x);
};
登入後複製

注意,由於f是作為一個函數變數傳入,所以f1變成了一個新的函數。

我們將f1改變一下,利用閉包可以寫成如下形式,則f1呼叫方式變成了f1( f)(x),而且得到的結果完全一樣。這就完成了f1的柯里化。

var f1 = function(f){
    return function(x){
        return f(x);
    }
};
var f2 = f1(f);
f2(x);
登入後複製

其實這個例子舉得不恰當,細心的同學可能會發現,f1雖然是一個新函數,但是f2f#是完全等效的,繞了半天,還是繞回來了。

這裡有一個很經典的例子:

['11', '11', '11'].map(parseInt) //[ 11, NaN, 3 ]
['11', '11', '11'].map(f1(parseInt)) //[ 11, 11, 11 ]
登入後複製

由於parseInt接受兩個參數,所以直接呼叫會有進位轉換的問題,參考「不願相離」的文章。

var f2 = f1(parseInt)f2parseInt由原來的接受兩個參數變成了只接受一個參數的新函數,從而解決這個進制轉換問題。透過我們的f1包裹以後就能夠運行出正確的結果了。

有同學覺得這個不算柯里化的應用,我覺得還是算吧,各位同學可以一起來討論下。

3. 函數柯里化進一步思考

如果說上一節的例子中,我們不是直接運行f(x),而是把函數f當做一個參數,結果會怎麼樣呢?我們來看下面這個範例:

假設f1傳回函數gg的作用域指向xs,函數f作為g的參數。最後我們可以寫成如下形式:

var f1 = function(f,xs){
    return g.call(xs,f);
};
登入後複製

實際上,用f1來取代g.call(xxx)的做法叫反柯里化。例如:

var forEach = function(xs,f){
    return Array.prototype.forEach.call(xs,f);
};
var f = function(x){console.log(x);};
var xs = {0:'peng',1:'chen',length:2};
forEach(xs,f);
登入後複製

反curring就是把原來已經固定的參數或this上下文等當作參數延遲到未來傳遞。
它能夠在很大程度上簡化函數,前提是你得習慣它。

拋開反柯里化,如果我們要柯里化f1怎麼辦?

使用閉包,我們可以寫成如下形式:

var f1 = function(f){
    return function(xs){
        return g.call(xs,f);
    }
};
var f2 = f1(f);
f2(xs);
登入後複製

f傳入f1中,我們就可以得到f2這個新函數。

只傳給函數一部分參數通常也叫做局部呼叫(partial application),能夠大量減少樣板檔案程式碼(boilerplate code)。

當然,函數f1傳入的兩個參數不一定要包含函數+非函數,可能兩個都是函數,也可能兩個都是非函數。

我個人覺得柯里化並非是必須的,而且不熟悉的同學閱讀起來可能會遇到麻煩,但是它能幫助我們理解JS中的函數式編程,更重要的是,我們以後在閱讀類似的程式碼時,不會感到陌生。知乎上羅納同學講的挺好:

並非「柯里化」對函數式程式設計有意義。而是,函數式程式設計在把函數當作一等公民的同時,就不可避免的會產生「柯里化」這種用法。所以它並不是因為「有什麼意義」才出現的。當然既然存在了,我們自然可以探討怎麼利用這個現象。

練習:

// 通过局部调用(partial apply)移除所有参数
var filterQs = function(xs) {
  return filter(function(x){ return match(/q/i, x);  }, xs);
};
//这两个函数原题没有,是我自己加的
var filter = function(f,xs){
    return xs.filter(f);
};
var match = function(what,x){
    return x.match(what);
};
登入後複製

分析:函數filterQs的作用是:傳入一個字串數組,過濾出包含'q'的字串,並組成一個新的數組回傳。

我們可以透過以下步驟得到函數filterQs

a. filter传入的两个参数,第一个是回调函数,第二个是数组,filter主要功能是根据回调函数过滤数组。我们首先将filter函数柯里化:

var filter = function(f){
    return function (xs) {
        return xs.filter(f);
    }
};
登入後複製

b. 其次,filter函数传入的回调函数是matchmatch的主要功能是判断每个字符串是否匹配what这个正则表达式。这里我们将match也柯里化:

var match = function(what){
    return function(x){
        return x.match(what);
    }
};
var match2 = match(/q/i);
登入後複製

创建匹配函数match2,检查字符串中是否包含字母q。

c. 把match2传入filter中,组合在一起,就形成了一个新的函数:

var filterQs =  filter(match2);
var xs = ['q','test1','test2'];
filterQs(xs);
登入後複製

从这个示例中我们也可以体会到函数柯里化的强大。所以,柯里化还有一个重要的功能:封装不同功能的函数,利用已有的函数组成新的函数。

4. 函数柯里化的递归调用

函数柯里化还有一种有趣的形式,就是函数可以在闭包中调用自己,类似于函数递归调用。如下所示:

function add( seed ) {
    function retVal( later ) {
        return add( seed + later );
    }
    retVal.toString = function() {
        return seed;
    };
    return retVal;
}
console.log(add(1)(2)(3).toString()); // 6
登入後複製

add函数返回闭包retVal,在retVal中又继续调用add,最终我们可以写成add(1)(2)(3)(...)这样柯里化的形式。
关于这段代码的解答,知乎上的李宏训同学回答地很好:

每调用一次add函数,都会返回retValue函数;调用retValue函数会调用add函数,然后还是返回retValue函数,所以调用add的结果一定是返回一个retValue函数。add函数的存在意义只是为了提供闭包,这个类似的递归调用每次调用add都会生成一个新的闭包。

5. 函数组合(compose)

函数组合是在柯里化基础上完成的:

var compose = function(f,g) {
  return function(x) {
    return f(g(x));
  };
};
var f1 = compose(f,g);
f1(x);
登入後複製

将传入的函数变成两个,通过组合的方式返回一个新的函数,让代码从右向左运行,而不是从内向外运行。

函数组合和柯里化有一个好处就是pointfree。

pointfree 模式指的是,永远不必说出你的数据。它的意思是说,函数无须提及将要操作的数据是什么样的。一等公民的函数、柯里化(curry)以及组合协作起来非常有助于实现这种模式。

// 非 pointfree,因为提到了数据:name
var initials = function (name) {
  return name.split(' ').map(compose(toUpperCase, head)).join('. ');
};

// pointfree
var initials = compose(join('. '), map(compose(toUpperCase, head)), split(' '));

initials("hunter stockton thompson");
// 'H. S. T'
登入後複製

以上是詳細介紹JavaScript函數柯里化的一些思考的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1666
14
CakePHP 教程
1425
52
Laravel 教程
1325
25
PHP教程
1272
29
C# 教程
1252
24
golang函數動態建立新函數的技巧 golang函數動態建立新函數的技巧 Apr 25, 2024 pm 02:39 PM

Go語言提供了兩種動態函數創建技術:closures和反射。 closures允許存取閉包作用域內的變量,而反射可使用FuncOf函數建立新函數。這些技術在自訂HTTP路由器、實現高度可自訂的系統和建置可插拔的元件方面非常有用。

C++ 函數命名中參數順序的考慮 C++ 函數命名中參數順序的考慮 Apr 24, 2024 pm 04:21 PM

在C++函數命名中,考慮參數順序至關重要,可提高可讀性、減少錯誤並促進重構。常見的參數順序約定包括:動作-物件、物件-動作、語意意義和遵循標準函式庫。最佳順序取決於函數目的、參數類型、潛在混淆和語言慣例。

excel函數公式大全 excel函數公式大全 May 07, 2024 pm 12:04 PM

1. SUM函數,用於對一列或一組單元格中的數字進行求和,例如:=SUM(A1:J10)。 2、AVERAGE函數,用於計算一列或一組儲存格中的數字的平均值,例如:=AVERAGE(A1:A10)。 3.COUNT函數,用於計算一列或一組單元格中的數字或文字的數量,例如:=COUNT(A1:A10)4、IF函數,用於根據指定的條件進行邏輯判斷,並返回相應的結果。

C++ 函式預設參數與可變參數的優缺點比較 C++ 函式預設參數與可變參數的優缺點比較 Apr 21, 2024 am 10:21 AM

C++函數中預設參數的優點包括簡化呼叫、增強可讀性、避免錯誤。缺點是限制靈活性、命名限制。可變參數的優點包括無限彈性、動態綁定。缺點包括複雜性更高、隱式型別轉換、除錯困難。

C++ 函式回傳參考型別有什麼好處? C++ 函式回傳參考型別有什麼好處? Apr 20, 2024 pm 09:12 PM

C++中的函數傳回參考類型的好處包括:效能提升:引用傳遞避免了物件複製,從而節省了記憶體和時間。直接修改:呼叫方可以直接修改傳回的參考對象,而無需重新賦值。程式碼簡潔:引用傳遞簡化了程式碼,無需額外的賦值操作。

如何在Java中寫出高效和可維護的函數? 如何在Java中寫出高效和可維護的函數? Apr 24, 2024 am 11:33 AM

編寫高效且可維護的Java函數的關鍵在於:保持簡潔。使用有意義的命名。處理特殊情況。使用適當的可見性。

自訂 PHP 函數和預定義函數之間有什麼區別? 自訂 PHP 函數和預定義函數之間有什麼區別? Apr 22, 2024 pm 02:21 PM

自訂PHP函數與預定義函數的差異在於:作用域:自訂函數僅限於其定義範圍,而預定義函數可在整個腳本中存取。定義方式:自訂函數使用function關鍵字定義,而預先定義函數則由PHP核心定義。參數傳遞:自訂函數接收參數,而預先定義函數可能不需要參數。擴充性:自訂函數可以根據需要創建,而預定義函數是內建的且無法修改。

C++ 函式異常進階:客製化錯誤處理 C++ 函式異常進階:客製化錯誤處理 May 01, 2024 pm 06:39 PM

C++中的異常處理可透過自訂異常類別增強,提供特定錯誤訊息、上下文資訊以及根據錯誤類型執行自訂操作。定義繼承自std::exception的異常類,提供特定的錯誤訊息。使用throw關鍵字拋出自訂異常。在try-catch區塊中使用dynamic_cast將捕獲到的異常轉換為自訂異常類型。在實戰案例中,open_file函數會拋出FileNotFoundException異常,捕捉並處理該異常可提供更具體的錯誤訊息。

See all articles