多亏了 Scott Sauyet 和 Bergi 的回答,我把头绕了过去。在这样做的过程中,我觉得仍然需要跳圈才能将所有部分放在一起。我将记录我在旅途中遇到的一些问题,希望对某些人有所帮助。
这是R.lift
我们尝试理解的示例:
var madd3 = R.lift((a, b, c) => a + b + c);
madd3([1,2,3], [1,2,3], [1]); //=> [3, 4, 5, 4, 5, 6, 5, 6, 7]
对我来说,在理解它之前需要回答三个问题。
- Fantasy-land的
Apply
规格(我将其称为Apply
)和Apply#ap
什么
- Ramda 的
R.ap
实现以及Array
与Apply
规范有什么关系
- 柯里化在其中起什么作用
R.lift
了解Apply
规范
在幻想世界中,对象在定义了方法Apply
时实现规范ap
(该对象还必须Functor
通过定义map
方法来实现规范)。
该ap
方法具有以下签名:
ap :: Apply f => f a ~> f (a -> b) -> f b
在幻想世界的类型签名符号中:
=>
声明类型约束,所以f
在上面的签名中指的是类型Apply
~>
声明方法声明,所以ap
应该是一个声明的函数,Apply
它包含一个我们称为的值a
(我们将在下面的示例中看到,一些幻想世界的实现与ap
此签名不一致,但想法是相同的)
假设我们有两个对象v
和u
( v = f a; u = f (a -> b)
) 因此这个表达式是有效v.ap(u)
的,这里需要注意一些事情:
v
并且u
都实现了Apply
. v
持有一个值,u
持有一个函数,但它们具有相同的“接口” Apply
(这将有助于理解下面的下一节,当谈到R.ap
and时Array
)
- 值
a
和函数a -> b
是无知的Apply
,函数只是对值进行变换a
。它将Apply
值和函数放入容器中,然后ap
将它们提取出来,在值上调用函数并将它们放回容器中。
了解Ramda
的R.ap
的签名R.ap
有两种情况:
Apply f => f (a → b) → f a → f b
: 这和上一节的签名非常相似Apply#ap
,区别在于ap
调用方式(Apply#ap
vs. R.ap
)和参数的顺序。
[a → b] → [a] → [b]
Apply f
: 如果我们用替换,这是版本Array
,还记得上一节中值和函数必须包装在同一个容器中吗?这就是为什么在使用R.ap
with Array
s 时,第一个参数是一个函数列表,即使你只想应用一个函数,也要把它放在一个 Array 中。
让我们看一个例子,我正在使用Maybe
from ramada-fantasy
,它实现Apply
了 ,这里的一个不一致之处是它Maybe#ap
的签名是:ap :: Apply f => f (a -> b) ~> f a -> f b
。似乎还有一些其他fantasy-land
的实现也遵循了这一点,但是,它不应该影响我们的理解:
const R = require('ramda');
const Maybe = require('ramda-fantasy').Maybe;
const a = Maybe.of(2);
const plus3 = Maybe.of(x => x + 3);
const b = plus3.ap(a); // invoke Apply#ap
const b2 = R.ap(plus3, a); // invoke R.ap
console.log(b); // Just { value: 5 }
console.log(b2); // Just { value: 5 }
理解的例子R.lift
在R.lift
带有数组的示例中,将一个 arity 为 3 的函数传递给R.lift
: var madd3 = R.lift((a, b, c) => a + b + c);
,它如何与三个数组一起工作[1, 2, 3], [1, 2, 3], [1]
?另请注意,它不是咖喱。
实际上在(委托给)的源代码中R.liftN
R.lift
,传入的函数是auto-curried,然后它遍历值(在我们的例子中,三个数组),减少到一个结果:在每次迭代中它调用ap
curried 函数和一个值(在我们的例子中,一个数组)。很难用语言解释,让我们看一下代码中的等价物:
const R = require('ramda');
const madd3 = (x, y, z) => x + y + z;
// example from R.lift
const result = R.lift(madd3)([1, 2, 3], [1, 2, 3], [1]);
// this is equivalent of the calculation of 'result' above,
// R.liftN uses reduce, but the idea is the same
const result2 = R.ap(R.ap(R.ap([R.curry(madd3)], [1, 2, 3]), [1, 2, 3]), [1]);
console.log(result); // [ 3, 4, 5, 4, 5, 6, 5, 6, 7 ]
console.log(result2); // [ 3, 4, 5, 4, 5, 6, 5, 6, 7 ]
一旦理解了计算的表达式result2
,例子就会变得清晰。
这是另一个示例,使用R.lift
on Apply
:
const R = require('ramda');
const Maybe = require('ramda-fantasy').Maybe;
const madd3 = (x, y, z) => x + y + z;
const madd3Curried = Maybe.of(R.curry(madd3));
const a = Maybe.of(1);
const b = Maybe.of(2);
const c = Maybe.of(3);
const sumResult = madd3Curried.ap(a).ap(b).ap(c); // invoke #ap on Apply
const sumResult2 = R.ap(R.ap(R.ap(madd3Curried, a), b), c); // invoke R.ap
const sumResult3 = R.lift(madd3)(a, b, c); // invoke R.lift, madd3 is auto-curried
console.log(sumResult); // Just { value: 6 }
console.log(sumResult2); // Just { value: 6 }
console.log(sumResult3); // Just { value: 6 }
Scott Sauyet 在评论中建议的一个更好的例子(他提供了很多见解,我建议你阅读它们)会更容易理解,至少它为读者指明了R.lift
计算Array
s 的笛卡尔积的方向。
var madd3 = R.lift((a, b, c) => a + b + c);
madd3([100, 200], [30, 40, 50], [6, 7]); //=> [136, 137, 146, 147, 156, 157, 236, 237, 246, 247, 256, 257]
希望这可以帮助。