js高階函數應用—函數柯里化和反柯里化(二)


上一篇文章中我們介紹了函數柯里化,順帶提到了偏函數,接下來我們繼續話題,進入今天的主題—函數的反柯里化。

在上一篇文章中柯里化函數你可能需要去敲許多代碼,理解很多代碼邏輯,不過這一節我們討論的反科里化你可能不需要看很多代碼邏輯,主要是理解反柯里化的核心思想,其實這種思想可能在你剛入門js時候就接觸到了,而且你幾乎天天在寫代碼過程中使用它。

首先需要理解反柯里化我們先來回顧上一節的內容,科里化簡單概括一下就是我們做了這樣一件事情:把接受多個參數的函數變換成接受一個單一參數的函數,並且返回新的函數來執行余下的數,公式表示基本模式為fn(a,b,c,d)=>fn(a)(b)(c)(d)。

那么很容易類比過來,反柯里化就是fn(a)(b)(c)(d)=>fn(a,b,c,d)是這樣嗎?其實就是這樣,不要懷疑就是這么簡單,只不過實現過程沒有這么直接明了而已,反柯里化大概是做了這么一件事情:把已經內置的特定使用場景的函數通過參數解放出來,提高函數的適用范圍。

轉化為公式:

curyyA=fn(a);

curryA(b)(c)(d)=>fn(a,b,c,d);

或者

curyyAb=fn(a)(b);//或者curyyAb=fn(a,b);

curryAb(c)(d)=>fn(a,b,c,d);

......以此類推

為了方便理解我們把上一節中的curry函數add版本拿過來

const curry = (fn, ...arg) => {
    let all = arg || [],
        length = fn.length;
    return (...rest) => {
        let _args = all.slice(0);
        _args.push(...rest);
        if (_args.length < length) {
            return curry.call(this, fn, ..._args);
        } else {
            return fn.apply(this, _args);
        }
    }
}
const add = curry((a, b) => a + b);
const add6 = add(6);
console.log(add6(1)); //7
console.log(add6(2)); //8

可以看到我們柯里化后的函數,每次執行了后返回的函數對於應用場景針對性越強,例如這個add6就是任意一個數和6的和,比起原來的add可以實現任意兩數的和,add6應用范圍更窄了,不過add6這個應用場景更有針對性,例如我們就需要一個任意數與6的和的時候這個add6就適應我們的場景。

這樣應該很容易理解了吧,如果還有問題我們來看一個更簡單的例子:

let getTag = (type) => {
    return `小明是一個${type}`
}
getTag('好學生');//小明是一個好學生
getTag('好老師');//小明是一個好老師

這個getTag函數根據傳入的參數返回小明的類型“小明是一個xxx”,不過這個函數是我們在專門得到小明的類型用的,如果我們現在有個需求需要的到“小華是一個xxx”,你總不會再寫一個函數

 getTagHua = (type) => `小華是一個${type}`

來得到“小華是一個xxx”的結果吧,就算剛入門編程語言時候我們也不會這么寫;要實現這個需求很簡單,再傳入一個參數就行了

let getTag2 = (name, type) => `${name}是一個${type}`

getTag2('小華', '好學生')//小華是個好學生

到這里我們可以對比一下getTag和getTag2這兩個函數,我們發現:

1.getTag是一個getTag2柯里化后帶着一個“小明”的內置參數的版本

2.getTag只針對得到小明類型的場景使用

3.getTag2是一個getTag泛化后的版本,適用范圍更廣

可以理解為getTag2是getTag的反柯里化函數,是不是很簡單,反柯里化的函數編程思想我們天天在用,只是沒注意到而已。現在再回去看之前的公式

curyyA=fn(a);

curryA(b)(c)(d)=>fn(a,b,c,d);

這種類型的轉化是不是就很容易理解了。

接下來我們接續我們的話題,我們現在知道了,反柯里化是一種編程思想,通過解析出函數內部限定條件,然后把限定條件當做參數傳給函數,從而提高函數使用范圍的一種編程思想,既然這樣我們就很容易理解我們下面這種情況了

class Book {
    constructor(name) {
        this.name = name;
    }
    static declare() {
        console.log('A Study Method');
    }
    sayName() {
        console.log(this.name);
    }
}

class Blog {
    constructor(name) {
        this.name = name;
    }
}
let book = new Book('javacript語言精粹');
let blog = new Blog('博客園');

book.sayName();                     //javacript語言精粹  
Book.prototype.sayName.call(blog); //博客園
Book.declare.call(blog);           //A Study Method

如上面我們在開發經常遇到的,blog想用book類的方法,用call和apply改變一下this指向就行了,我們常用的call,apply函數本身就是反科里化思想的體現,把函數的調用對象當做參數傳給函數,從而提高函數的適用范圍,類似於做了這么一件事情:

obj.fn(a,b)=>fn(object,a,b) 的轉化

通過轉化使得fn不再只適用於obj調用還可以讓其他的object調用,提高其適用范圍

那么我們把這個轉化過程用函數實現一下

Function.prototype.uncurrying = function() { //外邊這個不要寫成箭頭函數,因為我們具體反柯里化什么函數是我們調用時候才知道的
    return (obj, ...rest) => this.apply(obj, rest);
}

let sayName = Book.prototype.sayName.uncurrying();
let deClare = Book.declare.uncurrying();
sayName(blog) //博客園
deClare(blog) //A Study Method
deClare(book) //A Study Method

可以看到我們把Book類才能調用的靜態方法declare,和book實例的sayName反柯里化后,這種只針對類名調用的方法和只針對Book類對象調用的方法可以讓其他對象調用了。

當然我們也可以把一些js內置對象的方法uncurrying一下,比如一個字符串‘sdjkfjksfsdkslkdjsdf’,我們想把它每一個字符都拆出來放到一個數組中,或者每一項都拼個固定的字符再返回一數組,我們可以吧Array里的map方法uncurrying一下:

let map = Array.prototype.map.uncurrying();
console.log(map('yuweryiweryuie', val => val + 'test'))

當然很多js內置對象的方法可以uncurrying的,這里不做過多介紹,因為都是我們常用的call,apply的場景,即一些具有類似數據結構或者相同迭代器的對象我們通常會借用其他的對象方法

我們這里還給出一種上述uncurrying的實現方式:

Function.prototype.uncurrying = function() {
    return (...rest) => Function.prototype.call.apply(this, rest);
}

如果你不是很理解代碼也沒關系,沒太大影響,因為這個實現只是把之前的

Function.prototype.uncurrying = function() { //外邊這個不要寫成箭頭函數,因為我們具體反柯里化什么函數是我們調用時候才知道的
    return (obj, ...rest) => this.apply(obj, rest);
}

這個函數的傳obj的這個工作交給call來完成了,如果你還是不理解建議去mdn上看看call和apply用法,理一下邏輯就行,這里不做過多闡釋,好了我們currying和uncurrying的內容就到這了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM