为了账号安全,请及时绑定邮箱和手机立即绑定

无法理解 javascript 中的 compose reduce 示例中发生了什么?

无法理解 javascript 中的 compose reduce 示例中发生了什么?

慕尼黑8549860 2023-07-14 10:02:48
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, willreduce起作用,并且在其结束时fwill be test2,gwill beaddItemToCart然后test2作为累积值返回。然后我们称其(user1, { name: 'laptop', price: 876 })为参数传递,并g在其中称为addItemToCart。我不明白每次函数调用自身时如何g更改为applayTax, then addItemToPurchase, then 。emptytest2这是如何或为何发生的?
查看完整描述

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。它显然以相反的顺序循环遍历函数,并使用上一个函数的结果继续调用下一个函数。


查看完整回答
反对 回复 2023-07-14
?
潇潇雨雨

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点)。


查看完整回答
反对 回复 2023-07-14
?
HUH函数

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一起工作

在这里,我们尝试演示如何reducecomposeTwo多个函数组合为一个函数。

第一步,缺少init参数 to ,因此 JS 使用数组中的第一个值作为初始值,然后开始迭代第二个值。reduce所以reduce首先composeTwoemptyand调用addItemToPurchase,产生一个相当于

(...args) => empty (addItemsToPurchase (...args))

现在reduce将该函数 和applyTax传递给compose,产生一个类似的函数

(...args) => ((...args2) => empty (addItemsToPurchase (...args2))) (applyTax (...args))

现在其结构如下:

(x) => ((y) => f ( g (y)) (h (x))

其中x代表...argsy代表...args2f代表emptyg代表addItemsh代表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(箭头函数、休息/扩展和解构)使代码不仅更简洁,而且通常更容易理解。


查看完整回答
反对 回复 2023-07-14
  • 3 回答
  • 0 关注
  • 134 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号