如何在 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>
不值得
虽然这很有趣,但我绝不会在生产代码中使用它。现在读一遍,我开始明白了。不过一个月我就忘了,很多读者可能永远也不会明白。
无点可以产生一些优雅的代码。但只有在这样做时才值得使用;当它掩盖了您的意图时,请跳过它。
我的团队正在从 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>
不值得
虽然这很有趣,但我绝不会在生产代码中使用它。现在读一遍,我开始明白了。不过一个月我就忘了,很多读者可能永远也不会明白。
无点可以产生一些优雅的代码。但只有在这样做时才值得使用;当它掩盖了您的意图时,请跳过它。