如何正确使用 Ramda/JS 来组合函数

How to use Ramda/JS correctly for composing functions

我正在尝试使用函数式方法来解决特定问题,作为学习练习的一部分 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");
});

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

但是,这并不是我需要的有用结构。函数 'lookupByShortName' 不是通用的,它是用值 "t" 硬编码的。我想要的是省略 "t" 参数,这样当您调用 lookupByShortName 时,因为它应该被柯里化(通过 R.find),它应该 return 一个需要缺少参数的函数。所以如果我这样做,测试就会失败:

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

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

lookupByShortName("t")

或更具体("t" 附加在末尾):

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

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

1) Map short arg name to long option name map short name to long name POINTFREE: TypeError: lookupByShortName is not a function at Context.it.only (test/validator.spec.js:744:20)

所以我想到了另一个解决方案(这个方法不行,但我不明白为什么):

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

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

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

你能告诉我哪里出错了吗,谢谢。

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

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

我们从propEq开始:

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

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

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 会接受你的函数,就好像它可以工作一样,并且 return 类型为 [a] -> a 的函数。但是这个函数不会正常工作,因为 find 所做的是将 [a] 中的每个 a 传递给我们的 propEq('short') 直到其中一个 returns 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'),return 类型为 (a -> Boolean) 的新函数,第二个参数传递给 identity,它不进行转换,只传递值完整。然后将这两个结果传递给 find

useWith 和类似的 converge 是 Ramda 特有的。如果你不需要免分版(比如作为学习练习),这个第一个版本应该是可以的。