问题描述
var user1 = {
name: 'Nady',active: true,cart: [],purchase: [],};
var compose = function test1(f,g) {
return function test2(...args) {
return f(g(...args));
};
};
function userPurchase(...fns) {
return fns.reduce(compose);
}
userPurchase(
empty,addItemToPurchase,applayTax,addItemToCart
)(user1,{ name: 'laptop',price: 876 });
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: [] };
}
我不太理解这个例子。我尝试使用调试器逐步解决它,并得出以下结论:
当我调用函数userPurchase
时,reduce
将起作用,并且在其结尾处,f
将是test2
,而g
将是addItemToCart
test2
作为累积值返回。然后我们将其传递为(user1,price: 876 })
作为参数,并在其中调用g
的{{1}}。
我不明白每次函数addItemToCart
调用自己时,如何将g
分别更改为applayTax
,然后依次更改为addItemToPurchase
和empty
。
这种情况如何或为什么发生?
解决方法
可能会让您感到困惑的是字面意思是accumulator
。按照约定,这是reducer的第一个参数的名称。但是没有必要使用它来累积值。在这种情况下,它用于组成一系列功能。
reducer的第一个参数的实际含义是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()
TLDR
基本上所有这一切都在做:
let tmp;
tmp = addItemToCart(args);
tmp = applyTax(tmp);
tmp = addItemToPurchase(tmp);
tmp = empty(tmp);
可读性更高的版本
如果我们放弃reduce()
,有一种方法可以更容易阅读和理解,并且可以实现此逻辑。我个人喜欢map()
和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
版本的可读性高。显然,它以相反的顺序循环遍历这些函数,并继续使用前一个函数的结果调用下一个函数。
这完全有帮助吗?
db := GetDB()
defer db.Session.Close()
var tempMeet []models.Meetings
gtQuery := bson.M{"$gt": start}
ltQuery := bson.M{"$lt": end}
pipe := bson.M{
"$and": []bson.M{
bson.M{"startTime": gtQuery},bson.M{"endTime": ltQuery},},}
shared.BsonToJSONPrint(pipe)
err := db.C(models.COLLECTIONNAME).Find(pipe).All(&tempMeet)
if err != nil {
fmt.Println(err)
return nil,errors.New(err.Error())
}
return tempMeet,nil
假设您需要输入一个数字,将数字加10,然后再翻一番。您可以编写一些函数,例如:
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,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)); };
使用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_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,但是我们得到的仍然只是一个接受数字并返回数字的函数。到这里为止有意义吗? (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; };
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));
fns.reduce(compose);在代码中的userPurchase中,基本上与这里的forEach循环相同,但更简洁。这是否有意义,为什么add_ten_and_double_and_add_five是一个您可以传递数字的函数,而所有来自functions_to_compose的操作都被应用(从最后到第一)? (C点)。
,重命名
部分问题仅在于命名。这将有助于引入一个功能,并重命名两个功能。
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,applyTax,addItemToCart
)
userPurchase(user1,price: 876 });
//=> {active: true,name: "Nady",purchase: [{name: "laptop",price: 1138.8}]}
// other functions elided
composeTwo
(最初称为compose
)是一个接受两个函数并返回一个接受某个输入的新函数的函数,然后使用该输入调用第二个函数,然后用结果调用第一个函数。这是函数的直接数学组成。
composeMany
(最初称为-非常令人困惑-userPurchase
)扩展了此组合,使其可用于功能列表,使用reduce
到目前为止,从传递的参数开始,依次对管道的结果依次调用每个函数。请注意,它从列表的最后一项到第一个都有效。
我们用它来定义新的 userPurchase
,它将empty
,addItemToPurchase
,applyTax
和addItemToCart
传递给{ {1}}。这将返回一个 function ,然后按顺序应用它们,执行与pipeline
我们可以在以下代码片段中看到这一点:
function (...args) {return empty1(addItemToPurchase(applyTax(addItemToCart(...args))))}
var user1 = {
name: 'Nady',};
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,addItemToCart
)
console .log (
userPurchase(user1,price: 876 })
)
function addItemToCart(user,cart: [item] };
}
function applyTax(user) {
var { cart } = user;
var taxRate = 1.3;
var updatedCart = cart.map(function updateCartItem(item) {
return { ...item,cart: [] };
}
现代语法
但是,我会发现使用更现代的JS语法可以使这种方法更加简洁。这非常等效,但是更干净:
.as-console-wrapper {max-height: 100% !important; top: 0}
很显然,const composeTwo = (f,g) => (...args) =>
f (g (...args))
const composeMany = (...fns) =>
fns .reduce (composeTwo)
// .. other functions elided
const userPurchase = composeMany (
empty,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,addItemToCart
)
const user1 = {
name: 'Nady',};
console .log (
userPurchase (user1,price: 876 })
)
.as-console-wrapper {max-height: 100% !important; top: 0}
和reduce
如何一起工作
在这里,我们尝试演示composeTwo
与reduce
一起如何将多个功能组合为一个功能。
第一步,缺少composeTwo
的{{1}}参数,因此JS将数组中的第一个值用作初始值,并开始对第二个值进行迭代。因此,init
首先使用reduce
和reduce
调用composeTwo
,产生的功能等效于
empty
现在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
代表{{1} },而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,applyTax(taxRate),addItemToCart
)
// ...
userPurchase (1.3) (user1,price: 876 })
课程
-
功能组合是功能编程(FP)的重要组成部分。虽然具有
const someDistrictPurchase = userPurchase (1.12) // 12% tax someDistrictPurchase(user,item)
和var composeTwo = (f,cart: [item] }) const applyTax = (taxRate) => ({cart,price: item .price * taxRate })) }) const addItemToPurchase = (user) => ({ ...user,cart: [] }) const userPurchase = (taxRate) => composeMany ( empty,addItemToCart ) var user1 = { name: 'Nady',purchase: []} console .log ( userPurchase (1.3) (user1,price: 876 }) )
之类的功能会有所帮助,但如果它们具有易于理解的名称,那就更好了。 (请注意,.as-console-wrapper {max-height: 100% !important; top: 0}
是第一个名字的完全合法的名称。我在这里所做的更改只是为了使与composeTwo
的区别更加清楚。)我认为最大的问题是{{ 1}}作为组合函数。这使很多事情感到困惑。 -
现代JS(箭头功能,休息/扩展和解构)使代码不仅更简洁,而且通常更易于理解。