【问题标题】:lodash curry does not work on function returned by flow; lodash FP enough for FP?lodash curry 不适用于 flow 返回的函数; lodash FP 够 FP 吗?
【发布时间】:2016-08-19 13:56:48
【问题描述】:

lodash 流函数是真正的 compose 函数,还是看起来像一个,但经过优化以快速运行并牺牲了我期望的灵活性?我希望 flow 能够返回一个我可以 curry 的函数,但它却返回了一个使用 Javascript 的 arguments 关键字的函数。所以 curry 无法判断有待处理的参数,它会立即被调用。

足够直观地工作:

var add = function(x, y) { 
    return x + y 
};
var exclam = function(x) { 
    return x.toString() + "!"; 
}
exclam(1) // "1!"
add(1,2) // 3
var add1 = FP.curry(add)(1);
add1(4) // 5
var add1AndExclam = FP.flow([add1, exclam])
add1AndExclam(2) // "3!"

非直观结果:

addAndExclam = FP.flow([add, exclam])
/*
function(){
    var t=arguments,e=t[0];
    if(i&&1==t.length&&yi(e)&&e.length>=200)return i.plant(e).value();
    for(var u=0,t=r?n[u].apply(this,t):e;++u<r;)t=n[u].call(this,t);
    return t
}
*/
addAndExclam(1,2) // "3!"
add1AndExclamV2 = FP.curry(addAndExclam)(1) // "NaN!"`

寻找另一个库来帮助函数式编程范式是不是有点矫枉过正?我应该自己做曲子吗?我使用了 lodash,因为它已经在我的项目中了。文档使它看起来像 flow 应该是 lodash 的 compose。

我还发现很难在 lodash 的 each 中使用 data 参数(我想要一个类似 eachMyArrayName 快捷方式的东西)。无论我使用 curryRight 还是 lodash 对象占位符约定。

lodash FP 是否只是为了让 lodash 函数可以自动 curriable?还是我做错了什么,它可以用作主要的函数式编程助手?

编辑:

如果我愿意,我可以像这样包装函数,但它似乎违背了元编程的目的,让代码看起来像样板。

add1AndExclamV2 = FP.curry(function(x, y) { 
    return addAndExclam(x, y) 
})(1)
add1AndExclamV2(2)

“3!”`

【问题讨论】:

  • Curry 无法判断参数的数量,因为 javascript 中没有静态类型系统,每个函数都是可变参数。函数的 .length 属性只是一个提示,在 compose 函数中为可变参数、非curried函数设置它是一个麻烦。 Ramda 经历了这个(但很少失败),而 Underscore/Lodash 没有。如果您事先 curry 您的函数,compose 将按预期工作。

标签: javascript functional-programming lodash currying


【解决方案1】:

这只是基本的功能组合。 Lodash “flow” 和 Rambda “pipe” 可能发现很难命名这些函数,因为它们不适合通用。它们不是“真实的”,就像您在短语“真正的撰写功能”中使用单词 real 一样。

您可以使用comp2 将二元函数与一元函数组合在一起——关键是,“二元”函数必须采用柯里化形式,而不是采用元组

let f = x => y => ... 

而不是

let f = (x,y) => ...

回想一下,函数式编程起源于the lambda calculus,那里没有像函数这样的东西,它只接受一个参数。

const curry = f => x => y => f (x,y)
const comp = f => g => x => f (g (x))
const comp2 = comp (comp) (comp)

var add = function(x, y) { return x + y };
var exclam = function(x) { return x.toString() + "!"; }

console.log(exclam (1)) // "1!"
console.log(add (1,2)) // 3

var add1 = curry (add) (1)
console.log(add1 (4)) // 5

var addAndExclam = comp2 (exclam) (curry (add))
console.log(addAndExclam (1) (2)) // "3!"

我鼓励您使用 substitution model 来查看表达式的计算结果


将类型放在所有内容上有助于您更有效地推理程序

// curry :: ((a,b) -> c) -> a -> b -> c
const curry = f => x => y => f (x,y)

// comp :: (b -> c) -> (a -> b) -> (a -> c)
const comp = f => g => x => f (g (x))

// comp2 :: (c -> d) -> (a -> b -> c) -> (a -> b -> d)
const comp2 = comp (comp) (comp)

// add :: (Number,Number) -> Number
var add = function(x, y) { return x + y };

// exclam :: Number -> String
var exclam = function(x) { return x.toString() + "!"; }

console.log(exclam (1)) // "1!"
console.log(add (1,2)) // 3

// add1 :: Number -> Number
var add1 = curry (add) (1)
console.log(add1 (4)) // 5

// addAndExlam :: Number -> Number -> String
var addAndExclam = comp2 (exclam) (curry (add))
console.log(addAndExclam (1) (2)) // "3!"

关于您的评论:

我想如果我想编写一个特殊函数,其中一个嵌套函数需要两个参数,我会写出来

好主意。如果您发现自己四处寻找一个内置过程(由您的语言或某个库提供),那应该已经表明您应该先尝试将其写出来。至少与自己确认您正确理解了自己的需求。

这是完全可以接受的

const addAndExclam = (x,y) => exclam (add (x,y))

这也是

const addAndExclam = x => y => exclam (add (x,y))

如果你后来了解comp2,发现它可以更好地描述代码,那么你可以在那个时候实现它

const addAndExclam = comp2 (exclam) (curry (add))

【讨论】:

  • Ramda 的pipe 是一个真正的 组合函数,之所以这样命名,是因为compose 带有翻转的参数被称为“管道”。
  • 我不得不在控制台中玩了一会儿,但我想我终于明白了。因此 ramda 在幕后做了一些额外的工作以使其更加灵活。但是根据您对 comp 的定义(这是我在视频中看到的),无法判断返回的最终函数应该采用多个 arg。我必须明确地编写一个期望函数用 2.comp = f => g => (x, y) => f (g (x, y)) 调用的组合。这很有趣。
  • 另一方面,我可以 splat args 并且完全没有问题。 comp = f => g => function() { return f (g.apply(undefined, [].concat(Array.prototype.slice.apply(arguments || [])))) } ---- (splat从克罗克福德偷来的。我猜这是...... ES6 中的参数)
  • splat 的主要缺点与流程的缺点相同,它返回的函数不知道它应该有多少个参数,因此不能被柯里化。这无疑让我对为什么会发生这种情况有了更多的了解。我不确定我是否相信管道在边缘情况下的灵活性是一件坏事。
【解决方案2】:

var myAdd = function(x, y) { return x + y; }; var exclam = function(x) { return x.toString() + '!'; }; var addSclam = pipe(myAdd, exclam); addSclam(1,2); // "3!" var add1Sclam = curry( addSclam )(1); add1Sclam(500); // "501!"

看起来 ramda 做了对我来说很直观的事情。它为我的项目增加了 1.2mb,但我想它可以或多或少地用于替换 lodash。

【讨论】:

  • 这个问题很混乱。您正在尝试将二元函数 myAdd 与一元函数 exclam 组合在一起,因此遇到了麻烦。在这种情况下,Rambada 不是您的解决方案。它只会让您对函数式编程的误解持续存在。
  • 如果您认为 Rambda 的 pipe 为您提供了“真正的 compose 功能”,或者它正在做任何更“直观”的事情,那您又自欺欺人了。
  • add 和 pow 用于 flow 和 pipe 和 compose 的例子中,我认为这是一个标准的想法。唯一重要的是被调用的外部函数返回一个一元以向下馈送管道,而不是外部函数不能有多个参数。向您展示如何自己编写 compose 函数的示例似乎清楚地说明了这一点。所以我的问题是,当外部函数需要两个参数时,什么情况会让事情变得困难?我没有得到什么?也许你对 pipe 和 flow 的 arg 顺序感到困惑;它们与规范的组合相反。
  • 像您所做的那样在合成中使用二元函数并不能突出问题。 h(g(f(x,y))) 看起来可行,因为二元函数 f 仍然有一个返回值,该值通过一元 g 和一元 h 线程化。当您尝试组合多个二进制函数时会发生什么?使用_ 作为任何参数的视觉占位符,这是正确的m(l(k(_,_),_),_) 吗?或者这是 m(,l(,k(,)))`?三元函数呢?组合函数应该在外部函数的参数中采用哪个位置? ...
  • ... 如果每个函数的位置不统一怎么办?没有正确答案,任何支持这种“组合”的通用实用程序都在强化糟糕的设计。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-25
  • 1970-01-01
  • 1970-01-01
  • 2023-03-23
相关资源
最近更新 更多