2015-05-12 35 views
1

我正在學習函數式編程,我不知道是否有一種方法可以「合併」功能是這樣的:以功能的方式在JavaScript中「結合」功能?

function triple(x) { 
    return x * 3; 
} 
function plusOne(x) { 
    return x + 1; 
} 
function isZero(x) { 
    return x === 0; 
} 
combine(1); //1 
combine(triple)(triple)(plusOne)(1); // 10 
combine(plusOne)(triple)(isZero)(-1); // true 

如果對位是一個函數,它「融合」功能到自身,如果沒有它會返回最終結果。 謝謝!

+1

什麼是'para'? –

+0

@u_mulder參數我猜 – daremachine

+0

問題是不明確的,在「結合」功能 – gypsyCoder

回答

20

這個概念來自於數學可愛。它被稱爲function composition

 f(x) = y 
     g(y) = z 

    g(f(x)) = z 

    (g•f)(x) = z 

即最後一行被讀 「的的F x等於ž克」。 組成的功能是的消除。注意g(f(x)) = z我們取x輸入並得到z輸出。這完全跳過了中間的y。在這裏我們說我們刪除了點y

所以我們有一個組成函數(g•f)(x) = z,讓我們設置爲h(x)

h(x) = (g•f)(x) = z 

由於(g•f)已經是一個功能,我們可以刪除另一點,X

h(x) = (g•f)(x) = z 
    h = (g•f) 

函數組合是創造higher-order functions,並保持你的代碼非常乾淨的好方法。很容易明白爲什麼我們希望在你的Javascript中使用它。

「讓我們自己組合功能吧!」

好吧,你聽起來真的很興奮。在這裏,我們去...

function triple(x) { 
    return x * 3; 
} 

function plusOne(x) { 
    return x + 1; 
} 

function id(x) { return x; } 

function compN(funcs) { 
    return funcs.reduce(function(g, f) { 
    return function(x) { 
     return g(f(x)); 
    } 
    }, id); 
} 


var g = compN([triple, triple, plusOne]); 
// g(x) = (triple • triple • plusOne)(x) 

g(1); 
//=> 18 

評價

triple(triple(plusOne(1))) 
triple(triple(2)) 
triple(6) 
18 

如果您覺得滿意了答案,好。對於那些想探索構建更好的高階函數的人,請繼續!

以構建高階函數出的其他功能的概念,我們可以重構,並在我們的compN

定義擴大首先,讓我們瞭解如何compN正在評估的東西。比方說,我們要組成3個功能,一個bC現在

// g(x) = a(b(c(x))); 
// g(x) = (a•b•c)(x) 
// g = (a•b•c) 
var g = compN([a,b,c]) 

compN將調用陣列上reduce,並與我們的id函數初始化它(原因很明顯一點後來)

方式減少作品是將我們的數組中調用一次每個項目的這種內在的功能

function( g, f) { return function(x) { g(f(x)); }; } 

我特意添加空格把它與此表排隊低於

iteration g  f  return    explanation 
---------------------------------------------------------------------------- 
#1   id a  λx => g(f(x))  original return value 
         λx => id(a(x))  substitute for `g` and `f` 
         a'     we'll call this "a prime" 

#2   a' b  λx => g(f(x))  original return value 
         λx => a'(b(x))  substitute for `g` and `f` 
         b'     we'll call this "b prime" 

#3   b' c  λx => g(f(x))  original return value 
         λx => b'(c(x))  substitute for `g` and `f` 
         c'     we'll call this "c prime" 
              >> c' is the final return value << 

所以,當我們調用compN([a,b,c])c'是被退回的功能。

「那麼當我們通過一個參數調用該函數時會發生什麼,如c'(5)?」

alias   original   x   return 
---------------------------------------------------------------------------- 
c'(5);   λx => b'(c(x)) 5   b'(c(5)) 
b'(c(5))  λx => a'(b(x)) c(5)  a'(b(c(5))) 
a'(b(c(5)))  λx => id(a(x)) b(c(5)) id(a(b(c(5)))) <-- final return value 
換句話說

所以......

compN([a,b,c])(5) === id(a(b(c(5)))); 

「這是fricken甜的,但是這是一個有點辛苦爲我的大腦遵循。」

好吧,我同意,如果你和我一樣,當我在3個月內回到這段代碼時,我會抓着我的腦袋想知道它究竟做了什麼。

於是開始改善compN,讓我們先revist

// g(x) = a(b(c(x))); 
// g(x) = (a•b•c)(x) 
// g = (a•b•c) 
var g = compN([a,b,c]) 

如果我們看看另一個例子,也許我們會得到一個提示

[1,2,3].reduce(function(x,y) { return x + y; }, 0); 
//=> 6 

相同

((0 + 1) + 2) + 3 
//=> 6 

隱藏在那reduce的中間是這個功能

function (x,y) { return x + y; } 

看到了嗎?這看起來像一個非常基本的功能,不是嗎?也可以重複使用!如果你認爲add是一個好名字,你會是對的!讓我們來看看這再次

function add(x,y) { return x + y; } 
[1,2,3].reduce(add, 0); 
//=> 6 

這是超級容易跟隨減少了。我可以隨時回到該代碼並知道究竟是什麼發生了什麼事情。

我知道你在想什麼

1 + 2 + 3 

看起來極像是

a • b • c 

「也許如果我們提取compN的減少迭代器,我們可以簡化compN的定義.. 。「

這是我們的原創compN

// original 
function compN(fs) { 
    return fs.reduce(function(g, f) { 
    return function(x) { 
     return g(f(x)); 
    } 
    }, id); 
} 

讓我們到迭代,並調用它comp

function comp(g, f) { 
    return function(x) { 
    return g(f(x)); 
    } 
} 

var g = comp(triple)(plusOne); // λx => triple(plusOne(x)) 
g(1);        //  triple(plusOne(1)) 
//=> 6 

OK,讓我們看到修改後的compN現在

// revision 1 
function compN(fs) { 
    return fs.reduce(comp, id); 
} 

「粗糙的改善!所以,我們現在都做了什麼?」

Hahahahah,沒有。

看那個reduce只是坐在那裏。這是一個非常有用的功能,我敢肯定,我們可以使用,在噸地方

function reduce(f, i) { 
    return function(xs) { 
    return xs.reduce(f, i); 
    } 
} 

reduce(add, 0)([1,2,3]);  //=> 6 
reduce(comp, id)([a, b, c]); //=> λx => id(a(b(c(x)))) 

最後一行應該是下一版本的明顯提示我們compN功能

// revision 2 
function compN(fs) { 
    return reduce(comp, id)(fs); 
} 

「這並不顯得比改版1好得多......」

咄,我知道了!但是,你肯定會在每行的末尾看到懸掛的(fs),對吧?

你不會寫這個,對吧?

// useless wrapper 
function max(x) { 
    return Math.max(x); 
} 

max(3,1); 
//=> 3 

呃!這是一樣的

var max = Math.max; 
max(3,1); 
//=> 3 

所以我提出最後的reivision ...

// recap 
function reduce(f, i) { 
    return function(xs) { 
    return xs.reduce(f, i); 
    }; 
} 

function id(x) { return x; } 

function comp(g, f) { 
    return function(x) { 
    return g(f(x)); 
    }; 
} 

// revision 3 
var compN = reduce(comp, id); 

「和仍然以同樣的方式?」

是的,它確實!

function triple(x) { 
    return x * 3; 
} 

function plusOne(x) { 
    return x + 1; 
} 

var g = compN([triple, triple, plusOne]); // λx => id(triple(triple(plusOne(x)))) 
g(1);          //  id(triple(triple(plusOne(1)))) 
//=> 18 

「但是,這是爲什麼好?「

那麼它的簡單。當然,我們有一點點更多的代碼,但我們有可重複使用的功能,現在,而不是僅僅。每個函數都有一個簡單的任務,這是很容易立即識別。相比原有的功能,我們最終得到的代碼是多了很多聲明勢在必行,這是進一步強調了以下...


現在的東西真的很酷。我不會迪ve在這裏太深,但ES6使我們這個驚人。

// identical functionality as above 
let id = x => x; 
let reduce = (f,i) => xs => xs.reduce(f,i); 
let comp = (g,f) => x => g(f(x)); 
let compN = reduce(comp, id); 

// your functions 
let triple = x => x * 3; 
let plusOne = x => x + 1; 

// try it out! 
let g = compN([triple, triple, plusOne]); 
console.log(g(1)); 

//=> 18 

來吧,粘貼到一個Babel REPL以瞭解其工作

而這一切,鄉親!

+1

非常好!我花了很長時間閱讀它,這對我有很大的幫助。感謝您的傑出工作! – user3016883

+0

@ user3016883我發現了一對錯別字並將其修復。如果遇到麻煩,我希望編輯有幫助^。^ – naomik

+0

相關:[如何在JavaScript中正確地注入函數?](http://stackoverflow.com/q/27996544/783743) –

0

你可以簡單地調用返回函數值本身,例如:

plusOne(triple(triple(1))) // 10 
isZero(triple(plusOne(-1))) // true 
+0

你能解釋爲什麼我的答案是如此可憐的選民? –

+1

我沒有投你的帖子,但它並沒有真正回答這個問題。 OP想要以編程方式構建函數的「鏈」或「流水線」。它被稱爲[功能組合](http://en.wikipedia.org/wiki/Function_composition),JavaScript沒有內置的功能。你的回答很可能告訴OP他/她已經知道該怎麼做。 – naomik

+0

@naomik我會說這取決於OP意味着什麼「結合」,如果OP已經知道這個解決方案,我會想象他們會評論這一點。我只是說,除了做一個函數'F = f(g(x))',你可以通過執行'f(g(x))'來得到正確的結果,假設OP可能是一個初學者,也許可能不知道。它確實(在某種程度上)結合了功能,並確實產生了OP所需的正確結果,所以我不知道它不是一個答案。 –

0
function triple(x) { 
    return x * 3; 
} 
function plusOne(x) { 
    return x + 1; 
} 
function isZero(x) { 
    return x === 0; 
} 

var combine = function (v) { 
    var fn = []; 
    function _f(v) { 
     if (typeof v === 'function') { 
      fn.push(v); 
      return _f; 
     } else { 
      return fn.reduce(function (x, f) { return f(x); }, v); 
     } 
    } 
    return _f(v); 
}; 

var a, b; 
console.log(combine(1)); //1 
console.log(combine(triple)(triple)(plusOne)(1)); // 10 
console.log(combine(plusOne)(triple)(isZero)(-1)); // true 
console.log(a = combine(plusOne)); // function ... 
console.log(b = a(triple)); // function ... 
console.log(b(5)); // 18 
console.log(combine(triple)(plusOne)(triple)(plusOne)(triple)(plusOne)(1)); // 40 
// @naomik's examples 
var f = combine(triple); 
var g = combine(triple)(triple); 
console.log(f(1)); // 3 
console.log(g(1)); // 9 (not 6 as you stated) 
+0

做兩個函數:'var f = combine(triple); var g = combine(triple)(triple);'調用'f(1); // 27',然後調用'g(1)// 1'。顯然'f(1)//應該是3','g(1)//應該是6'。這會失敗,因爲您在創建新鏈之前依賴於用戶完成鏈。問題是'combine.fn'是有狀態的,隨後對'combine'的調用將導致其他函數的'fn'狀態被突變。 – naomik

+0

我知道這個問題,但是你期望用'combine(triple)(triple);'結果是什麼結果?該通話始終以一個值結束,所以規則是函數*值(bfn)。 –

+0

我希望'combine(triple)(triple)'爲我創建一個新的函數,我可以指定給變量,稍後調用或作爲值傳遞給另一個函數。就像我上面用'f'和'g'演示的一樣。 – naomik