3 回答

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

TA貢獻(xiàn)1833條經(jīng)驗 獲得超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);
假設(shè)您需要輸入一個數(shù)字,加十,然后加倍。您可以編寫一些函數(shù),例如:
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,我們創(chuàng)建了一個與原始 add_ten_and_double 函數(shù)執(zhí)行相同操作的函數(shù)。到目前為止這有意義嗎?(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));
現(xiàn)在我們已經(jīng)使用一個函數(shù)和一個由另外兩個函數(shù)組成的函數(shù)運(yùn)行 compose,但我們得到的仍然只是一個接受數(shù)字并返回數(shù)字的函數(shù)。到目前為止這有意義嗎?(B點)。
如果我們想添加更多函數(shù),那么我們最終會在代碼中進(jìn)行大量 compose 調(diào)用,因此我們可以只說“給我所有這些函數(shù)的組合”,它可能看起來像:
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 循環(huán)執(zhí)行相同的操作,但更簡潔。為什么 add_ten_and_double_and_add_ Five 是一個可以傳入數(shù)字的函數(shù),并且functions_to_compose 中的所有操作都應(yīng)用于該數(shù)字(從最后到第一個),這是否有意義?(C點)。

TA貢獻(xiàn)1836條經(jīng)驗 獲得超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) 是一個函數(shù),它接受兩個函數(shù)并返回一個新函數(shù),該函數(shù)接受一些輸入,使用該輸入調(diào)用第二個函數(shù),然后使用結(jié)果調(diào)用第一個函數(shù)。這是函數(shù)的簡單數(shù)學(xué)組合。
composeMany(最初稱為 - 非常令人困惑 - userPurchase)將此組合擴(kuò)展為在函數(shù)列表上工作,從傳遞的參數(shù)開始reduce,按順序調(diào)用到目前為止管道結(jié)果上的每個函數(shù)。請注意,它的工作范圍是從列表中的最后一項到第一項。
我們用它來定義 new userPurchase,它將empty、addItemToPurchase和傳遞給。這會返回一個函數(shù),然后該函數(shù)將按順序應(yīng)用它們,執(zhí)行相當(dāng)于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}
現(xiàn)代語法
然而,我會發(fā)現(xiàn)使用更現(xiàn)代的 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接受兩個函數(shù)并返回一個函數(shù)。在了解和理解后.reduce,應(yīng)該清楚composeMany接受一個函數(shù)列表并返回一個新函數(shù)。此版本也將此更改應(yīng)用于其余功能,可在以下代碼段中找到:
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
多個函數(shù)組合為一個函數(shù)。
第一步,缺少init
參數(shù) to ,因此 JS 使用數(shù)組中的第一個值作為初始值,然后開始迭代第二個值。reduce
所以reduce
首先composeTwo
用empty
and調(diào)用addItemToPurchase
,產(chǎn)生一個相當(dāng)于
(...args) => empty (addItemsToPurchase (...args))
現(xiàn)在reduce
將該函數(shù) 和applyTax
傳遞給compose
,產(chǎn)生一個類似的函數(shù)
(...args) => ((...args2) => empty (addItemsToPurchase (...args2))) (applyTax (...args))
現(xiàn)在其結(jié)構(gòu)如下:
(x) => ((y) => f ( g (y)) (h (x))
其中x
代表...args
、y
代表...args2
、f
代表empty
、g
代表addItems
、h
代表applyTax
。
但右側(cè)是一個函數(shù) ( ),并對其應(yīng)用了(y) => f ( g ( y))
值。h (x)
這與y
在主體中替換為h(x)
, 產(chǎn)生相同f (g (h (x)))
,因此該函數(shù)相當(dāng)于 (x) => f (g (h (x)))
,并且通過替換我們的原始值,最終結(jié)果為
(...args) => empty (addItemsToPurchase (applyTax ( ...args)))
請注意,在構(gòu)建函數(shù)時,現(xiàn)在不會對函數(shù)應(yīng)用值。當(dāng)調(diào)用結(jié)果函數(shù)時就會發(fā)生這種情況。記憶中,這還是類似的東西(...args) => ((...args2) => empty (addItems (...args2))) (applyTax (...args))
。但這個合乎邏輯的版本展示了它是如何工作的。
當(dāng)然,我們現(xiàn)在再次這樣做addItemToCart
:
(...args) => ((...args2) => empty (addItemsToPurchase (applyTax ( ...args2)))) (addItemToCart (...args))
通過同樣的應(yīng)用,我們得到等價的
(...args) => empty (addItems (applyTax ( addItemToCart (...args))))
這就是這些函數(shù)的組成的基本定義。
清理稅率
硬編碼的稅率有些奇怪。我們可以通過在調(diào)用中使用參數(shù)來解決這個問題userPurchase
。這也允許我們清理該applyTax
函數(shù):
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 })
請注意,此參數(shù)的柯里化性質(zhì)讓我們選擇僅應(yīng)用此值來獲取特定于稅率的函數(shù):
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}
教訓(xùn)
函數(shù)組合是函數(shù)式編程(FP)的重要組成部分。雖然擁有
composeTwo
和 等函數(shù)很有幫助composeMany
,但如果它們具有易于理解的名稱,那就更好了。(請注意,這compose
是第一個的完全合法的名稱。我在這里的更改只是為了使區(qū)別更清晰。)在我看來,最大的問題是作為組合函數(shù)composeMany
的原始名稱。userPurchase
這讓很多事情變得混亂。現(xiàn)代 JS(箭頭函數(shù)、休息/擴(kuò)展和解構(gòu))使代碼不僅更簡潔,而且通常更容易理解。
添加回答
舉報