如何在 Ramda 中以无点样式组合柯里化函数?

How to compose curried functions in a point free style in Ramda?

我的团队正在从 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 for getStuff.

如果没有 getOtherStuff 功能,我们可以 R.curry myFunc

是否有一个优雅的解决方案,而且没有任何意义?这在 FP 中似乎很常见。

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

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 重述为 return 一对,将 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人喜欢,虽然对我来说很有挑战性。

我最大的建议是开始考虑将所有函数作为一元函数。

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

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

注意 o is just a (Ramda-curried) binary version of Ramda's usual variadic compose 函数。

我真的没弄明白。我作弊了。如果您可以阅读 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 的魔术柯里化,我猜这与此有关。

此代码段展示了它的实际效果,您的函数已被删除。

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>

不值得

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

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