微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

为什么这个组合函数在作为引用传递时会以错误的顺序应用函数?

如何解决为什么这个组合函数在作为引用传递时会以错误的顺序应用函数?

问题

问题是组合函数有时会以错误的顺序应用其函数,但仅当它作为引用传递时(例如,mapValues(compose(appendB,appendA))(wordNumberMap))。

如果组合函数直接应用于给定的值,它会以正确的顺序应用其函数(例如,mapValues(v => compose(appendB,appendA)(v))(wordNumberMap))。

composition-direct-vs-ref

问题

  1. 是什么导致通过引用传递组合函数错误地应用其函数
  2. 在通过引用(例如,mapValues(compose(appendB,appendA)))传递组合函数时,可以做什么(如果可能)来保持函数的正确顺序?

代码

以下代码显示了所有单独工作的函数,并演示了如何直接使用组合函数而通过引用传递组合函数失败:

// All functions:
const appendA = v => v + 'a';
const appendB = v => v + 'b';
const appendC = v => v + 'c';
const compose = (...functions) => initial => functions.reverse().reduce((result,next) => next(result),initial);
const mapValues = (mapper = v => v) => (obj = {}) => Object.keys(obj).reduce((result,key) => ({ ...result,[key]: mapper(obj[key],key) }),{});

// `compose` works:
compose(appendC,appendB,appendA)(1); //=> "1abc"

// `mapValues` works:
const wordNumberMap = { one: 1,two: 2,three: 3,four: 4,five: 5 };
// `appendA` applied directly:
mapValues(v => appendA(v))(wordNumberMap); //=> {one: "1a",two: "2a",three: "3a",four: "4a",five: "5a"}
// `appendA` applied by reference:
mapValues(appendA)(wordNumberMap); //=> {one: "1a",five: "5a"}

// Applying `compose` directly applies the functions in the correct order:
mapValues(v => compose(appendC,appendA)(v))(wordNumberMap) //=> {one: "1abc",two: "2abc",three: "3abc",four: "4abc",five: "5abc"}

// Passing `compose` by reference applies the functions in the incorrect order:
mapValues(compose(appendC,appendA))(wordNumberMap); //=> {one: "1abc",two: "2cba",four: "4cba",five: "5abc"}
// The returned object shows that every other entry in the returned object has a value where the append functions have been applied incorrectly in reverse order - why does this happen?

解决方法

在以下代码中,您创建了一个闭包,这意味着您的 ...functions 数组在调用之间共享

const compose = (...functions) => initial => ...

问题的根源在于 the reverse() method reverses an array in place,所以如果你多次调用 compose(appendC,appendB,appendA) 的返回值(这就是你“通过引用传递撰写”时所做的事情 em>),您将一次又一次地反转相同的数组。


当您“直接应用组合”时,这种影响会减轻,因为您在每次迭代时都会创建一个全新的组合函数:

mapValues(v => compose(appendC,appendA)(v))

因此,您在 ...functions 中对 compose 进行变异这一事实不是问题,因为返回值是短暂的。


作为一条规则:永远不要在函数式编程中改变你的输入。这是一个副作用。如果适合您的风格,您可以随意改变局部变量,但不适合您的输入。

特别是当你咖喱(闭包模式 a => b => ...)时,因为你在调用之间共享第一个输入。


您可以像这样实现 compose

const composition = (f,g) => x => g(f(x));
const id = x => x;
const compose = (...fns) => fns.reduceRight(composition,id);

或者如果你喜欢

const apply = (x,f) => f(x);
const compose = (...fns) => x => fns.reduceRight(apply,x);

或者如果您需要将多个参数应用于入口点

const apply = (x,f) => f(x);
const compose = (...fns) => (...xs) => {
  const last = fns[fns.length -1];
  const init = fns.slice(0,-1)
  return init.reduceRight(apply,last(...xs))
};

请注意,在第一个版本中,当您调用 compose 时会发生迭代,而在其他两个版本中,它会在您应用带参数的返回函数时发生。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。