【问题标题】:How to compose curried functions in a point free style in Ramda?如何在 Ramda 中以无点风格组合咖喱函数?
【发布时间】:2020-07-22 10:36:18
【问题描述】:

我的团队正在从 Lodash 迁移到 Ramda,并进入函数式编程风格的更深层次。我们一直在用compose 等进行更多试验,并遇到了这种模式:

const myFunc = state => obj => id => R.compose(
  R.isNil,
  getOtherStuff(obj),
  getStuff(state)(obj)
)(id)

(我们当然可以省略=> id(id) 部分。为清楚起见添加。)

换句话说,我们的应用程序中有很多函数(在某些情况下是 React+Redux),我们需要组合接受相似参数的函数,或者最后一个函数需要在传递给compose 行中的下一个函数。在我给出的示例中,这将是 id 然后是 obj 然后是 state 对于 getStuff

如果不是 getOtherStuff 函数,我们可以 R.curry myFunc

是否有一个优雅的解决方案,将是免费的?这在 FP 中似乎很常见。

【问题讨论】:

  • 我觉得这很易读(尤其是没有id。)我建议你不要争取点免费。当它使代码更具可读性时使用它,而不是当您需要努力寻找版本时使用它。 Ramda(免责声明:我是它的主要作者之一)有时会考虑可能使其中一些更简单的技术(最近在#2930),但没有一个真正获得牵引力。
  • wat @ScottSauyet 说。我从思考中受益,无点是一个很好的思考练习 - 但目标是可维护性。
  • 你在创建一个选择器吗?
  • @OriDrori 大多数时候是的,虽然这种模式也出现在我们的其他地方。
  • 您是否使用reselect 来进行reducers?它与 Ramda 和无点风格完美结合。

标签: javascript functional-programming ramda.js


【解决方案1】:

我不知道你为什么不能咖喱:

const myFunc = curry(state, obj) => R.compose(
  R.isNil,
  getOtherStuff(obj),
  getStuff(state)(obj)
));

const myFunc = curry(state, obj, id) => R.compose(
  R.isNil,
  getOtherStuff(obj),
  getStuff(state)(obj)
)(id));

我不确定我是否在这里看到了无点解决方案(就目前而言)。有一些不太直观的组合器可能适用。我要考虑的另一件事是 getStuff 和 getOtherStuff 函数的签名是否按正确的顺序排列。如果按以下顺序定义它们可能会更好:obj、state、id。

问题是 obj 在两个不同的功能中需要。也许重述 getStuff 以返回一对,而 getOtherStuff 以获取一对。

const myFunc = R.compose(
  R.isNil,         // val2 -> boolean
  snd,             // (obj, val2) -> val2
  getOtherStuff,   // (obj, val) -> (obj, val2)
  getStuff         // (obj, state, id) -> (obj, val)
);

myFunc(obj)(state)(id)

我发现将多个参数函数视为接受单个参数的函数很有帮助,该参数恰好是某种元组。

getStuff = curry((obj, state, id) => {
   const val = null;
   return R.pair(obj, val);
}

getOtherStuff = curry((myPair) => {
   const obj = fst(myPair)
   const val2 = null;
   return R.pair(obj, val2);
}

fst = ([f, _]) => f
snd = ([_, s]) => s

=====

根据关于组合器的问题进行更新。从http://www.angelfire.com/tx4/cus/combinator/birds.html 有八哥(S)组合:

λa.λb.λc.(ac)(bc)

以更 es6 的方式编写

const S = a => b => c => a(c, b(c))

或者一个接受三个参数 a,b,c 的函数。我们将 c 应用于 a 留下一个新函数,并将 c 应用于 b 留下任何立即应用于从 c 应用于 a 得到的函数的内容。

在你的例子中我们可以这样写

S(getOtherStuff, getStuff, obj)

但是现在我看到它可能不起作用。因为 getStuff 在被应用到 getOtherStuff 之前并不完全满足......您可以开始拼凑一个难题的解决方案,这有时很有趣,但也不是您想要在生产代码中使用的东西。有本书https://en.wikipedia.org/wiki/To_Mock_a_Mockingbird人们喜欢它,尽管它对我来说很有挑战性。

我最大的建议是开始将所有功能都视为一元。

【讨论】:

  • 我喜欢 Pair 的想法。感谢您的建议。
  • @tjklemz 我发布了其中的一些内容。 Ramda 有一个 pair 函数,但它看起来没有 fst 或 snd 函数来解构 - 它们很容易添加,但由于它们不受支持,我可能也会添加我自己的 pair 函数,所以我不受实施Ramda 内部的变化。
  • 您提到有“一些不太直观的组合器可能适用”。出于好奇,你能给我指点这方面的一些资源吗?在这种特殊情况下可能无济于事,但要尽可能多地学习。
  • @tjklemz 有这个(有点难读)angelfire.com/tx4/cus/combinator/birds.html 但你可以写一些扭曲的东西const special = (f1, f2, obj) => compose( f1(obj), f2(obj) ) 然后将其应用为const myComposition => R.compose( R.isNil, special(getOtherStuff, getStuff) )) 然后myComposition(obj)(state)(id) - 鉴于一切都是咖喱等. 但实际上它对可读性没有帮助。
  • 看起来八哥就是你想要的。我会补充我的答案。
【解决方案2】:

这是不将无点推得太远的一个理由。我设法制作了上述内容的无点版本。但我不能真正理解它,我真的怀疑我的代码的大多数读者也会。在这里,

const myFunc2 = o (o (o (isNil)), o (liftN (2, o) (getOtherStuff), getStuff)) 

注意 o 只是 Ramda 常用的可变参数 compose 函数的(Ramda-curried)二进制版本。

我并没有真正弄清楚这一点。我作弊了。如果你能阅读 Haskell 代码并用它写一些基本的东西,你可以使用精彩的 Pointfree.io 站点将有指向的代码转换为无指向的代码。

我输入了你的函数的这个 Haskell 版本:

\state -> \obj -> \id -> isNil (getOtherStuff obj (getStuff state obj id))

得到了这个:

((isNil .) .) . liftM2 (.) getOtherStuff . getStuff

其中,有点磕磕绊绊,我能够转换到上面的版本。我知道我必须使用o 而不是compose,但花了一点时间才明白我必须使用liftN (2, o) 而不仅仅是lift (o)。我还没有试图弄清楚为什么,但 Haskell 真的不明白 Ramda 的魔法柯里化,我猜它与此有关。

这个 sn-p 显示了它的作用,你的函数被存根了。

const isNil = (x) => 
  `isNil (${x})`

const getStuff = (state) => (obj) => (id) =>
  `getStuff (${state}) (${obj}) (${id})`

const getOtherStuff = (obj) => (x) =>
  `getOtherStuff (${obj}) (${x})`

const myFunc = state => obj => id => R.compose(
  isNil,
  getOtherStuff (obj),
  getStuff (state) (obj)
)(id)


const myFunc2 = o (o (o (isNil)), o (liftN (2, o) (getOtherStuff), getStuff)) 


console .log ('Original   : ', myFunc ('state') ('obj') ('id'))
console .log ('Point-free : ', myFunc2 ('state') ('obj') ('id'))
.as-console-wrapper {min-height: 100% !important; top: 0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
<script> const {o, liftN} = R                                      </script>

不值得

虽然这很有趣,但我永远不会在生产代码中使用它。现在读了一遍,我开始明白了。但是一个月后我就忘记了,很多读者可能永远都不会明白。

无点可以产生一些优雅的代码。但只有在这样做时才值得使用;如果它掩盖了您的意图,请跳过它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-07-11
    • 1970-01-01
    • 1970-01-01
    • 2017-02-22
    • 1970-01-01
    • 1970-01-01
    • 2013-08-26
    • 1970-01-01
    相关资源
    最近更新 更多