3 回答

TA貢獻1802條經驗 獲得超10個贊
可能讓您感到困惑的是從字面上理解這個術語accumulator。按照慣例,這是減速器的第一個參數的名稱。但沒有必要用它來積累價值。在本例中,它用于組合一系列函數。
減速器第一個參數的真正含義是previouslyReturnedValue:
function compose(previouslyReturnedValue, g) {
return function (...args) {
return previouslyReturnedValue(g(...args));
};
}
那么讓我們來看看這個循環:
[empty, addItemToPurchase, applayTax, addItemToCart].reduce(
(f,g) => {
return (...args) => {
return f(g(...args));
}
}
);
第一輪循環,f = empty和g = addItemToPurchase。這將導致compose返回:
return (...args) => {
return empty(addItemToPurchase(...args));
}
離開數組變為:[applayTax, addItemToCart]
第二輪循環f = (...args) => {return empty(addItemToPurchase(...args))}和g = applyTax。這將導致compose返回:
return (...args) => {
return empty(addItemToPurchase(applyTax(...args)));
}
我們繼續這個邏輯,直到我們最終compose返回完整的函數鏈:
return (...args) => {
return empty(addItemToPurchase(applyTax(addItemToCart(...args))));
}
同一流程的替代視圖
如果上面的內容有點難以理解,那么讓我們為f每個循環中的匿名函數命名。
在第一輪中我們得到:
function f1 (...args) {
return empty(addItemToPurchase(...args));
}
在第二輪中我們得到:
function f2 (...args) {
return f1(applyTax(...args));
}
在最后一輪我們得到:
function f3 (...args) {
return f2(addItemToCart(...args));
}
f3返回的就是這個函數reduce()。所以當你調用reduce的返回值時它會嘗試做:
f2(addItemToCart(...args))
哪個將調用addItemToCart(),然后調用f2哪個將執行:
f1(applyTax(...args))
哪個將調用applyTax(),然后調用f1哪個將執行:
empty(addItemToPurchase(...args))
哪個會調用addItemToPurchase()然后調用empty()
總長DR
基本上所有這些都是在做:
let tmp;
tmp = addItemToCart(args);
tmp = applyTax(tmp);
tmp = addItemToPurchase(tmp);
tmp = empty(tmp);
更易讀的版本
有一種方法可以實現這個邏輯,如果我們放棄的話,它會更具可讀性和更容易理解reduce()。map()我個人喜歡像and這樣的函數數組方法,reduce()但這是一種罕見的情況,常規for循環可能會導致代碼更具可讀性和可調試性。這是一個簡單的替代實現,它執行完全相同的操作:
function userPurchase(...fns) {
return function(...args) {
let result = args;
// The original logic apply the functions in reverse:
for (let i=fns.length-1; i>=0; i--) {
let f = fns[i];
result = f(result);
}
return result;
}
}
userPurchase就我個人而言,我發現使用循環的實現for比版本更具可讀性reduce。它顯然以相反的順序循環遍歷函數,并使用上一個函數的結果繼續調用下一個函數。

TA貢獻1833條經驗 獲得超4個贊
這有幫助嗎?
var user1 = {
name: 'Nady',
active: true,
cart: [],
purchase: [],
};
function compose(f, g) {
const composition = function(...args) {
console.log('f name', f.name);
console.log('g name', g.name);
return f(g(...args));
};
Object.defineProperty(composition, 'name', {
value: 'composition_of_' + f.name + '_and_' + g.name,
writable: false
});
return composition;
};
function userPurchase(...fns) {
return fns.reduce(compose);
}
function addItemToCart(user, item) {
return { ...user, cart: [item] };
}
function applayTax(user) {
var { cart } = user;
var taxRate = 1.3;
var updatedCart = cart.map(function updateCartItem(item) {
return { ...item, price: item.price * taxRate };
});
return { ...user, cart: updatedCart };
}
function addItemToPurchase(user) {
return { ...user, purchase: user.cart };
}
function empty(user) {
return { ...user, cart: [] };
}
const result = userPurchase(
empty,
addItemToPurchase,
applayTax,
addItemToCart
)(user1, { name: 'laptop', price: 876 });
console.log(result);
假設您需要輸入一個數字,加十,然后加倍。您可以編寫一些函數,例如:
const add_ten = function(num) { return num + 10; };
const double = function(num) { return num * 2; };
const add_ten_and_double = function(num) { return double(add_ten(num)); };
你也可以寫同樣的東西:
function compose(outer_function, inner_function) {
return function(num) {
return outer_function(inner_function(num));
};
};
const add_ten = function(num) { return num + 10; };
const double = function(num) { return num * 2; };
const add_ten_and_double = compose(double, add_ten);
console.log('typeof add_ten_and_double:', typeof add_ten_and_double);
console.log('add_ten_and_double:', add_ten_and_double(4));
使用 compose,我們創建了一個與原始 add_ten_and_double 函數執行相同操作的函數。到目前為止這有意義嗎?(A點)。
如果我們決定添加五個,我們可能會得到:
function compose(outer_function, inner_function) {
return function(num) {
return outer_function(inner_function(num));
};
};
const add_ten = function(num) { return num + 10; };
const double = function(num) { return num * 2; };
const add_five = function(num) { return num + 5; };
const add_ten_and_double_and_add_five = compose(compose(add_five, double), add_ten);
console.log('typeof add_ten_and_double_and_add_five :', typeof add_ten_and_double_and_add_five);
console.log('add_ten_and_double_and_add_five :', add_ten_and_double_and_add_five(4));
現在我們已經使用一個函數和一個由另外兩個函數組成的函數運行 compose,但我們得到的仍然只是一個接受數字并返回數字的函數。到目前為止這有意義嗎?(B點)。
如果我們想添加更多函數,那么我們最終會在代碼中進行大量 compose 調用,因此我們可以只說“給我所有這些函數的組合”,它可能看起來像:
function compose(outer_function, inner_function) {
return function(num) {
return outer_function(inner_function(num));
};
};
const add_ten = function(num) { return num + 10; };
const double = function(num) { return num * 2; };
const add_five = function(num) { return num + 5; };
functions_to_compose = [add_five, double, add_ten];
let composition;
functions_to_compose.forEach(function(to_compose) {
if(!composition)
composition = to_compose;
else
composition = compose(composition, to_compose);
});
const add_ten_and_double_and_add_five = composition;
console.log('typeof add_ten_and_double_and_add_five:', typeof add_ten_and_double_and_add_five);
console.log('add_ten_and_double_and_add_five:', add_ten_and_double_and_add_five(4));
fns.reduce(compose); 代碼中的 userPurchase 基本上與此處的 forEach 循環執行相同的操作,但更簡潔。為什么 add_ten_and_double_and_add_ Five 是一個可以傳入數字的函數,并且functions_to_compose 中的所有操作都應用于該數字(從最后到第一個),這是否有意義?(C點)。

TA貢獻1836條經驗 獲得超4個贊
重命名
部分問題只是在于命名。引入另外一項功能并重命名另外兩項功能將有所幫助。
var composeTwo = function test1(f, g) {
return function test2(...args) {
return f(g(...args));
};
};
function composeMany(...fns) {
return fns.reduce(composeTwo);
}
const userPurchase = composeMany(
empty,
addItemToPurchase,
applyTax,
addItemToCart
)
userPurchase(user1, { name: 'laptop', price: 876 });
//=> {active: true, cart: [], name: "Nady", purchase: [{name: "laptop", price: 1138.8}]}
// other functions elided
composeTwo(最初稱為compose) 是一個函數,它接受兩個函數并返回一個新函數,該函數接受一些輸入,使用該輸入調用第二個函數,然后使用結果調用第一個函數。這是函數的簡單數學組合。
composeMany(最初稱為 - 非常令人困惑 - userPurchase)將此組合擴展為在函數列表上工作,從傳遞的參數開始reduce,按順序調用到目前為止管道結果上的每個函數。請注意,它的工作范圍是從列表中的最后一項到第一項。
我們用它來定義 new userPurchase,它將empty、addItemToPurchase和傳遞給。這會返回一個函數,然后該函數將按順序應用它們,執行相當于applyTaxaddItemToCartpipelinefunction (...args) {return empty1(addItemToPurchase(applyTax(addItemToCart(...args))))}
我們可以在這段代碼中看到它的實際效果:
var user1 = {
name: 'Nady',
active: true,
cart: [],
purchase: [],
};
var composeTwo = function test1(f, g) {
return function test2(...args) {
return f(g(...args));
};
};
function composeMany (...fns) {
return fns.reduce(composeTwo);
}
const userPurchase = composeMany (
empty,
addItemToPurchase,
applyTax,
addItemToCart
)
console .log (
userPurchase(user1, { name: 'laptop', price: 876 })
)
function addItemToCart(user, item) {
return { ...user, cart: [item] };
}
function applyTax(user) {
var { cart } = user;
var taxRate = 1.3;
var updatedCart = cart.map(function updateCartItem(item) {
return { ...item, price: item.price * taxRate };
});
return { ...user, cart: updatedCart };
}
function addItemToPurchase(user) {
return { ...user, purchase: user.cart };
}
function empty(user) {
return { ...user, cart: [] };
}
.as-console-wrapper {max-height: 100% !important; top: 0}
現代語法
然而,我會發現使用更現代的 JS 語法會更干凈。這是非常等價的,但更干凈:
const composeTwo = (f, g) => (...args) =>
f (g (...args))
const composeMany = (...fns) =>
fns .reduce (composeTwo)
// .. other functions elided
const userPurchase = composeMany (
empty,
addItemToPurchase,
applyTax,
addItemToCart
)
很明顯,這里composeTwo接受兩個函數并返回一個函數。在了解和理解后.reduce,應該清楚composeMany接受一個函數列表并返回一個新函數。此版本也將此更改應用于其余功能,可在以下代碼段中找到:
var composeTwo = (f, g) => (...args) =>
f (g (...args))
const composeMany = (...fns) =>
fns .reduce (composeTwo)
const addItemToCart = (user, item) =>
({ ...user, cart: [item] })
const applyTax = (user) => {
var { cart } = user;
var taxRate = 1.3;
var updatedCart = cart .map (item => ({ ...item, price: item .price * taxRate }))
return { ...user, cart: updatedCart };
}
const addItemToPurchase = (user) =>
({ ...user, purchase: user.cart })
const empty = (user) =>
({ ...user, cart: [] })
const userPurchase = composeMany (
empty,
addItemToPurchase,
applyTax,
addItemToCart
)
const user1 = {
name: 'Nady',
active: true,
cart: [],
purchase: [],
};
console .log (
userPurchase (user1, { name: 'laptop', price: 876 })
)
.as-console-wrapper {max-height: 100% !important; top: 0}
如何reduce
以及如何composeTwo
一起工作
在這里,我們嘗試演示如何reduce
將composeTwo
多個函數組合為一個函數。
第一步,缺少init
參數 to ,因此 JS 使用數組中的第一個值作為初始值,然后開始迭代第二個值。reduce
所以reduce
首先composeTwo
用empty
and調用addItemToPurchase
,產生一個相當于
(...args) => empty (addItemsToPurchase (...args))
現在reduce
將該函數 和applyTax
傳遞給compose
,產生一個類似的函數
(...args) => ((...args2) => empty (addItemsToPurchase (...args2))) (applyTax (...args))
現在其結構如下:
(x) => ((y) => f ( g (y)) (h (x))
其中x
代表...args
、y
代表...args2
、f
代表empty
、g
代表addItems
、h
代表applyTax
。
但右側是一個函數 ( ),并對其應用了(y) => f ( g ( y))
值。h (x)
這與y
在主體中替換為h(x)
, 產生相同f (g (h (x)))
,因此該函數相當于 (x) => f (g (h (x)))
,并且通過替換我們的原始值,最終結果為
(...args) => empty (addItemsToPurchase (applyTax ( ...args)))
請注意,在構建函數時,現在不會對函數應用值。當調用結果函數時就會發生這種情況。記憶中,這還是類似的東西(...args) => ((...args2) => empty (addItems (...args2))) (applyTax (...args))
。但這個合乎邏輯的版本展示了它是如何工作的。
當然,我們現在再次這樣做addItemToCart
:
(...args) => ((...args2) => empty (addItemsToPurchase (applyTax ( ...args2)))) (addItemToCart (...args))
通過同樣的應用,我們得到等價的
(...args) => empty (addItems (applyTax ( addItemToCart (...args))))
這就是這些函數的組成的基本定義。
清理稅率
硬編碼的稅率有些奇怪。我們可以通過在調用中使用參數來解決這個問題userPurchase
。這也允許我們清理該applyTax
函數:
const applyTax = (taxRate) => ({cart, ...rest}) => ({
... rest,
cart: cart .map (item => ({ ...item, price: item .price * taxRate }))
})
const userPurchase = (taxRate) => composeMany (
empty,
addItemToPurchase,
applyTax(taxRate),
addItemToCart
)
// ...
userPurchase (1.3) (user1, { name: 'laptop', price: 876 })
請注意,此參數的柯里化性質讓我們選擇僅應用此值來獲取特定于稅率的函數:
const someDistrictPurchase = userPurchase (1.12) // 12% tax
someDistrictPurchase(user, item)
我們可以在另一個片段中看到這一點:
var composeTwo = (f, g) => (...args) =>
f (g (...args))
const composeMany = (...fns) =>
fns .reduce (composeTwo)
const addItemToCart = (user, item) =>
({ ...user, cart: [item] })
const applyTax = (taxRate) => ({cart, ...rest}) => ({
... rest,
cart: cart .map (item => ({ ...item, price: item .price * taxRate }))
})
const addItemToPurchase = (user) =>
({ ...user, purchase: user.cart })
const empty = (user) =>
({ ...user, cart: [] })
const userPurchase = (taxRate) => composeMany (
empty,
addItemToPurchase,
applyTax(taxRate),
addItemToCart
)
var user1 = { name: 'Nady', active: true, cart: [], purchase: []}
console .log (
userPurchase (1.3) (user1, { name: 'laptop', price: 876 })
)
.as-console-wrapper {max-height: 100% !important; top: 0}
教訓
函數組合是函數式編程(FP)的重要組成部分。雖然擁有
composeTwo
和 等函數很有幫助composeMany
,但如果它們具有易于理解的名稱,那就更好了。(請注意,這compose
是第一個的完全合法的名稱。我在這里的更改只是為了使區別更清晰。)在我看來,最大的問題是作為組合函數composeMany
的原始名稱。userPurchase
這讓很多事情變得混亂。現代 JS(箭頭函數、休息/擴展和解構)使代碼不僅更簡潔,而且通常更容易理解。
添加回答
舉報