多亏了 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.apand时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#apvs. R.ap)和参数的顺序。
[a → b] → [a] → [b]Apply f: 如果我们用替换,这是版本Array,还记得上一节中值和函数必须包装在同一个容器中吗?这就是为什么在使用R.apwith Arrays 时,第一个参数是一个函数列表,即使你只想应用一个函数,也要把它放在一个 Array 中。
让我们看一个例子,我正在使用Maybefrom 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.liftNR.lift,传入的函数是auto-curried,然后它遍历值(在我们的例子中,三个数组),减少到一个结果:在每次迭代中它调用apcurried 函数和一个值(在我们的例子中,一个数组)。很难用语言解释,让我们看一下代码中的等价物:
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.lifton 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计算Arrays 的笛卡尔积的方向。
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]
希望这可以帮助。