如何正确使用Ramda / JS来编写函数

时间:2017-05-17 19:34:10

标签: javascript functional-programming ramda.js

我正在尝试使用功能性方法来解决特定问题,作为学习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"应转换为" turky"如选项中所定义。

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

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

所以这里,lookupByShortName应该成为一个需要一个缺少参数的函数,所以从理论上讲,我认为我应该能够调用这个函数,如下所示:

lookupByShortName("t")

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

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

...但我错了,因为这不起作用,测试失败了:

  

1)将短的arg名称映射到long选项名称map short name to long   名字POINTFREE:        TypeError: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上完成了一系列文章,尽管我的理解更好,但我还没有。

你能否解释我出错的地方,谢谢。

1 个答案:

答案 0 :(得分:3)

第一个问题是你的代码不起作用的原因。

最简单的解释方法是使用功能签名。

我们从propEq开始:

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

这就像哈贝尔这样的语言。 propEq是一个函数,它接受一个String并返回一个带有任意类型的函数的函数,并返回一个接受Object并返回一个Boolean的函数。您可以更明确地编写它,如

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时,我们不会得到一个布尔值,而是从Object到一个函数Boolean

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

第二个问题是如何使其发挥作用。

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

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。如果您不需要无点版本(例如,作为学习练习),可能会优先考虑第一个版本。