【问题标题】:How to use Ramda/JS correctly for composing functions如何正确使用 Ramda/JS 编写函数
【发布时间】:2017-05-17 19:34:10
【问题描述】:

作为学习 Ramda.js 练习的一部分,我正在尝试使用函数式方法来解决特定问题。

所以我有这个测试:

it.only("map short name to long name POINTFREE", () => {
  let options = [ { long: "perky", short: "p" }, { long: "turky", short: "t" } ];

  let lookupByShortName = R.find(R.propEq("short", "t"));
  let result = lookupByShortName(options);
  expect(result).to.have.property("long", "turky");
});

“选项”用作查找序列。我需要通过引用选项序列将一系列指定为单个字符的字符串转换为等效的更长名称。所以字符“t”应该转换成options中定义的“turky”。

但是,这并不是我需要它有用的方式。 'lookupByShortName' 函数不是通用的,它是用值“t”硬编码的。我想要的是省略“t”参数,这样当你调用lookupByShortName时,因为它应该被curried(通过R.find),它应该返回一个需要缺少参数的函数。所以如果我这样做,测试就会失败:

let lookupByShortName = R.find(R.propEq("short"));

所以在这里,lookupByShortName 应该成为一个需要单个缺失参数的函数,所以理论上,我认为我应该可以如下调用该函数:

lookupByShortName("t")

或更具体地说(“t”附加在末尾):

let lookupByShortName = R.find(R.propEq("short"))("t");

...但是我弄错了,因为这样不行,测试失败:

1) 将短 arg 名称映射到长选项名称 将短名称映射到长 名称无点: 类型错误:lookupByShortName 不是函数 在 Context.it.only (test/validator.spec.js:744:20)

所以我想到了另一种解决方案(不起作用,但我不明白为什么):

因为“t”是传递给 R.propEq 的第二个参数,所以使用 R.__ 占位符,然后在末尾传入“t”:

let lookupByShortName = R.find(R.propEq("short", R.__))("t");

我已经在blog 上完成了一系列文章,虽然我的理解更好,但我还没有。

您能否说明我哪里出错了,谢谢。

【问题讨论】:

    标签: javascript functional-programming ramda.js


    【解决方案1】:

    第一个问题是为什么你的代码不起作用。

    解释这一点的最简单方法是使用函数签名。

    我们从propEq开始:

    propEq :: String -> a -> Object -> Boolean
    

    这就是像 Hakell 这样的语言。 propEq 是一个函数,它接受一个字符串并返回一个接受任意类型的函数并返回一个接受一个对象并返回一个布尔值的函数。你可以像这样更明确地写它

    propEq :: String -> (a -> (Object -> Boolean))
    

    你可以用类似这样的语法来调用它:

    propEq('short')('t')({ long: "perky", short: "p" }); //=> false
    

    Ramda 有一个稍微不同的想法,即您不必一次通过这些。所以调用 Ramda 的函数有几种同样有效的方法:

    propEq :: String -> (a -> (Object -> Boolean))
              String -> ((a, Object) -> Boolean)
              (String, a) -> (Object -> Boolean)
              (String, a, Object) -> Boolean
    

    这分别意味着这样称呼它:

    propEq('short')('t')({ long: "perky", short: "p" }); //=> false
    propEq('short')('t', { long: "perky", short: "p" }); //=> false
    propEq('short', 't')({ long: "perky", short: "p" }); //=> false
    propEq('short', 't', { long: "perky", short: "p" }); //=> false
    

    接下来是find,如下所示:

    find :: (a -> Boolean) -> [a] -> a 
    

    出于类似的原因,这在 Ramda 中意味着其中之一:

    find :: (a -> Boolean) -> ([a] -> a)
         :: ((a -> Boolean), [a]) -> a
    

    当你打电话时

    find(propEq('short'))
    

    您正试图将a -> Object -> Boolean 作为第一个参数传递给find,它希望作为它的第一个参数a -> Boolean。尽管 Javascript 不是强类型的,而且 Ramda 也没有尝试为强类型提供太多帮助,但是你有一个类型不匹配。你实际上已经沉没了,尽管 Ramda 会接受你的函数,就好像它会工作一样,并返回一个 [a] -> a 类型的函数。但是这个函数不能正常工作,因为find 所做的是将[a] 中的每个a 传递给我们的propEq('short'),直到其中一个返回true。这永远不会发生,因为propEq('short') 的签名是a -> Object -> Boolean,所以当我们传递a 时,我们得到的不是布尔值,而是一个从ObjectBoolean 的函数。

    这种类型不匹配是您当前方法不起作用的原因。


    第二个问题是如何让它发挥作用。

    最直接的方法是使用这样的东西:

    let lookupByShortName = (abbrv, options) => R.find(R.propEq("short", abbrv), options);
    lookupByShortName('t', options); //=> {"long": "turky", "short": "t"}
    

    这是干净、清晰的代码。我可能会这样离开它。但如果你真的希望它是无点的,Ramda 为这种情况提供了useWith。你可以这样使用它:

    let lookupByShortName = R.useWith(R.find, [R.propEq('short'), R.identity]);
    

    这可以看作是两个参数的(柯里化)函数。第一个参数传递给propEq('short'),返回一个(a -> Boolean) 类型的新函数,第二个参数传递给identity,它不进行任何转换,只是完整地传递值。然后将这两个结果传入find

    useWith 和类似的converge 非常特定于 Ramda。如果您不需要免点版本(例如,作为学习练习),那么第一个版本可能更可取。

    【讨论】:

    • 非常感谢 Scott 的回答,在我正确评论之前,我需要一些时间来考虑并正确理解它。现在干杯...
    猜你喜欢
    • 2018-11-12
    • 2019-08-19
    • 1970-01-01
    • 1970-01-01
    • 2017-11-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多