【问题标题】:When is a => f(a) not equivalent to f?a => f(a) 什么时候不等于 f?
【发布时间】:2019-05-05 22:32:11
【问题描述】:

lodash 新手并尝试使用它以获得更多理解。我不明白以下代码的行为。

了解_.curry 的arity 参数后,我有一个代码sn-p,它产生的结果对我来说似乎很奇怪。

const words = ['jim', 'john'];
const pad10 = words =>
    _.map(words, word => _.pad(word, 10));

console.log(pad10(words)); // [ '   jim    ', '   john   ' ]

const flipMap = _.flip(_.map);
const flipPad = _.flip(_.pad);

const curriedFlipMap = _.curry(flipMap, 2);

const pad10v2 = curriedFlipMap(word => flipPad(' ', 10, word));

console.log(pad10v2(words)); // [ '   jim    ', '   john   ' ]

const curriedFlipPad = _.curry(flipPad, 3);
const padWord10 = curriedFlipPad(' ', 10);
const pad10v3 = curriedFlipMap(word => padWord10(word));

console.log(pad10v3(words)); // [ '   jim    ', '   john   ' ]

const pad10v4 = curriedFlipMap(padWord10);
console.log(pad10v4(words)); // [ 'jim,john', 'jim,john' ]
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>

我不明白最后一个 console.log 的输出。在我看来,当需要一个 arg 函数时,我只是将 a => f(a) 替换为 f。

【问题讨论】:

  • 这称为eta转换。它假定柯里化函数。因此,将arr.map(f)arr.map(a => f(a)) 进行比较。你能说出区别吗?查看Array.prototype.map 能够处理什么样的函数。这是 Javascript 中两种形式的唯一区别。
  • 我知道什么是eta转换。那不是我的问题。这就是我试图用来简化代码的方法。然而,在这种情况下,它给出了一个令人惊讶的(错误的)结果。请注意,如果您将 'flipPad' 的声明更改为 const flipPad = (padding, length, text) => _.pad(text, length, padding);(IOW - 只需自己进行翻转),则上述代码将按预期工作。这让我相信使用“_.flip”会发生一些我不知道的事情。
  • 我相信 JavaScript 中的箭头函数语法将函数的 this 隐式绑定到调用站点,而传递函数则不会(完整的 function(...) { ... } 语法也不需要显式 @987654330 @ 称呼)。我不知道这是否适用于这种情况。
  • 您说您知道什么是 eta 转换,但您的问题标题仍然是 When is a => f(a) not equivalent to f?。我告诉过你什么时候不等价:在 Javascript 的多参数函数的上下文中。顺便说一句,jim,john 之类的字符串通常是在 Array<String> 值的隐式 toString 转换时构造的。
  • @bbarrington 如果您使用 lodash 进行函数式编程,请查看 lodash/fp

标签: javascript functional-programming lodash currying


【解决方案1】:

是的,fa => f(a) 在 JavaScript 中是有区别的。考虑以下示例:

const array = (...args) => args;

const arrayEta = a => array(a);

console.log(array(1, 2, 3)); // [1, 2, 3]

console.log(arrayEta(1, 2, 3)); // [1]

你看到问题了吗?当我调用arrayEta(1, 2, 3) 时,它会扩展为(a => array(a))(1, 2, 3),而beta 会减少为array(1),因为从未使用过23。但是,非 eta 扩展版本是 array(1, 2, 3)。这是您的代码的问题:

const words = ["jim", "john"];

const flipMap = _.flip(_.map);
const flipPad = _.flip(_.pad);

const curriedFlipMap = _.curry(flipMap, 2);
const curriedFlipPad = _.curry(flipPad, 3);

const padWord10 = curriedFlipPad(" ", 10);

const pad10v4 = curriedFlipMap((...args) => {
    console.log(args); // args is an array of 3 arguments
    return padWord10(...args);
});

console.log(pad10v4(words)); // ["jim,john", "jim,john"]
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>

请注意,args 是一个 array of three arguments、单词、索引和数组。因此,curriedFlipMap(padWord10) 实际上 eta 等价于 curriedFlipMap((word, index, array) => padWord10(word, index, array))。它不等于 curriedFlipMap(word => padWord10(word))

因此,您的函数调用减少如下:

  padWord10("jim", 0, ["jim", "john"])
= curriedFlipPad(" ", 10)("jim", 0, ["jim", "john"])
= _curry(flipPad, 3)(" ", 10)("jim", 0, ["jim", "john"])
= _.pad(["jim", "john"], 0, "jim", 10, " ")
= _.pad(["jim", "john"], 0, "jim")

如您所见,您为函数 _.pad 提供了 5 个参数,其中忽略了最后 2 个参数。因此,它将 ["jim", "john"] 转换为字符串,然后为其添加填充。

无论如何,解决方案是在这种情况下不进行 eta 转换。顺便说一句,如果您想使用 Lodash 进行函数式编程,请改用 lodash/fp

【讨论】:

  • 非常感谢您的详细检查。如果我可以再请求您一次,请向我解释为什么下面的函数add2与原来的函数padWord10不同。它们都是 3 个 arg 咖喱函数,提供前 2 个 args。下面的示例按预期工作。 const sum = (x, y, z) => x + y + z; const curriedSum = _.curry(sum); const add2 = curriedSum(1)(1); const map = (f, array) => array.map(f); const curriedMap = _.curry(map); const mapAdd2 = curriedMap(add2); const r = mapAdd2([1, 2, 3, 4]); console.log(r); // [ 3, 4, 5, 6 ]
  • 你没有flipsum函数。
  • 好的。回到我原来的前提——我不明白_.flip 到底在做什么。 _curry(_.flip(f)) 创建一个可变参数函数(非柯里化),它的参数从右到左。因此,在下面的代码中,我可以使用可变数量的参数调用curriedFlippedSum,但只会评估最后三个。如果这对其他人来说很明显,我很抱歉。对我来说当然不是。我很感激帮助。 const sum = (x, y, z) => x + y + z; const flippedSum = _.flip(sum); const curriedFlippedSum = _.curry(flippedSum); console.log(curriedFlippedSum(1, 2, 3, 4, 5)); // 12
  • 使用flip 会颠倒参数的顺序。因此,curriedFlippedSum(1, 2, 3, 4, 5)sum(5, 4, 3, 2, 1) 相同,而 5 + 4 + 3 等于 12
  • 是的,但问题是你不能将它用作柯里化函数:const sum = (x, y, z) => x + y + z; const flippedSum = _.flip(sum); const curriedFlippedSum = _.curry(flippedSum); const add2 = curriedFlippedSum(1)(1); // TypeError: curriedFlippedSum(...) is not a function const four = add2(2); 我最初的目标是将数据参数移动到参数列表的末尾,然后通过柯里化,简化使用_.map_.map(f)(data)。但显然,使用这种方法是行不通的。无论如何,我感谢您的帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-09-07
  • 2019-10-22
  • 1970-01-01
  • 1970-01-01
  • 2021-07-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多