无法在Ramda.js中绕着"电梯"环绕我的脑袋

dip*_*rus 22 javascript functional-programming lifting ramda.js

查看Ramda.js的来源,特别是"升力"功能.

电梯

liftN

这是给定的例子:

var madd3 = R.lift(R.curry((a, b, c) => a + b + c));

madd3([1,2,3], [1,2,3], [1]); //=> [3, 4, 5, 4, 5, 6, 5, 6, 7]
Run Code Online (Sandbox Code Playgroud)

因此结果的第一数目是容易的,a,b,和c,是每个阵列的所有的第一元素.第二个对我来说并不容易理解.参数是每个数组的第二个值(2,2,未定义)还是第一个数组的第二个值以及第二个和第三个数组的第一个值?

即使无视这里发生的事情的顺序,我也没有真正看到它的价值.如果我在没有lift它的情况下执行它,我将最终将数组concat作为字符串.这似乎有点像,flatMap但我似乎无法遵循其背后的逻辑.

Sco*_*yet 34

Bergi的答案很棒.但另一种思考方式是更具体一点.Ramda确实需要在其文档中包含一个非列表示例,因为列表并没有真正捕获它.

让我们来看一个简单的功能:

var add3 = (a, b, c) => a + b + c;
Run Code Online (Sandbox Code Playgroud)

这适用于三个数字.但是如果你有容器拿着数字怎么办?也许我们有Maybe.我们不能简单地将它们加在一起:

const Just = Maybe.Just, Nothing = Maybe.Nothing;
add3(Just(10), Just(15), Just(17)); //=> ERROR!
Run Code Online (Sandbox Code Playgroud)

(好吧,这是Javascript,它实际上不会在这里抛出错误,只是尝试连接它本不应该的东西......但它肯定不会做你想要的!)

如果我们能够将该功能提升到容器的水平,它将使我们的生活更轻松.Bergi所指出的lift3是在Ramda 中实现的liftN(3, fn),以及光泽,lift(fn)它简单地使用了所提供功能的arity.所以,我们可以这样做:

const madd3 = R.lift(add3);
madd3(Just(10), Just(15), Just(17)); //=> Just(42)
madd3(Just(10), Nothing(), Just(17)); //=> Nothing()
Run Code Online (Sandbox Code Playgroud)

但这个提升的功能并不知道我们的容器有什么具体的,只有他们实现的ap.Ramda实现ap在一个列表的方式类似于在列表的双重交叉应用功能的元组,所以我们也可以这样做:

madd3([100, 200], [30, 40], [5, 6, 7]);
//=> [135, 136, 137, 145, 146, 147, 235, 236, 237, 245, 246, 247]
Run Code Online (Sandbox Code Playgroud)

这就是我的想法lift.它需要一个在某些值的级别上工作的函数,并将其提升到在这些值的容器级别上工作的函数.

  • 啊,如果我理解正确的话,它“学习”如何通过“.ap”的实现来使用容器。我还看到 `Just.prototype.ap = function(m) { return m.map(this.value); };` 在 `ramda-fantasy` 仓库中。`ap` 是一个典型的函数式编程,还是 ramda/javascript 就是这样处理 `lift` 的? (2认同)

Dap*_* Li 11

感谢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]
Run Code Online (Sandbox Code Playgroud)

对我来说,在理解之前三个问题需要回答.

  1. Fantasy-landApply规范(我将其称为Apply)以及Apply#ap它的作用
  2. Ramda的R.ap实现以及ArrayApply规范有什么关系
  3. currying扮演什么角色 R.lift

了解Apply规范

在fantasy-land中,对象Applyap定义方法时实现规范(该对象还必须Functor通过定义map方法来实现规范).

ap方法具有以下特征:

ap :: Apply f => f a ~> f (a -> b) -> f b
Run Code Online (Sandbox Code Playgroud)

fantasy-land的类型签名表示法中:

  • =>声明类型约束,所以f在签名上面指的是类型Apply
  • ~>声明方法声明,因此ap应在声明的函数Apply围绕我们称其为价值包a(我们将在下面的例子中看到,一些幻想土地的实现ap都没有与此签名一致,但这个想法是一样的)

假设我们有两个对象vu(v = f a; u = f (a -> b))因此这个表达式是有效的v.ap(u),有些事情需要注意:

  • vu实施Apply.v持有价值,u拥有的功能,但它们具有相同的"接口"的Apply(这将在了解下一节帮助,当谈到R.apArray)
  • 价值a和功能a -> b都是无知的Apply,功能只是改变了价值a.它将Apply值和函数放在容器中并将ap它们提取出来,调用值上的函数并将它们放回去.

了解RamdaR.ap

签名R.ap有两种情况:

  1. Apply f => f (a ? b) ? f a ? f b:这与上Apply#ap一节中的签名非常相似,区别在于如何ap调用(Apply#apvs. R.ap)和params的顺序.
  2. [a ? b] ? [a] ? [b]:这是如果更换版本Apply fArray,请记住,价值和功能必须被包裹在上一节中相同的容器?这就是为什么当使用R.ap带有ArrayS,第一个参数是函数的列表,即使你想申请只有一个功能,把它放在一个阵列.

让我们来看一个例子,我使用的是Mayberamada-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 }
Run Code Online (Sandbox Code Playgroud)

理解的例子 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,传入的函数是自动curry,然后它遍历值(在我们的例子中,三个数组),减少到结果:在每次迭代中它调用apcurried函数和一个值(在我们的例子中,一个数组).用文字解释很难,让我们看看代码中的等价物:

const R = require('ramda');
const Maybe = require('ramda-fantasy').Maybe;

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 ]
Run Code Online (Sandbox Code Playgroud)

一旦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 }
Run Code Online (Sandbox Code Playgroud)

Scott Sauyet在评论中提出了一个更好的例子(他提供了一些见解,我建议你阅读它们)会更容易理解,至少它指出了读者R.lift计算笛卡尔积的方向Array.

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]
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助.

  • 这很棒.我将发表一些评论,希望能够对棘手的问题进行一些澄清.但首先,有一个建议:来自文档的例子很难理解.像这样的人可能会有所帮助:`madd3([100,200],[30,40,50],[6,7]); // => [136,137,146,147,156,157,236,237,246,247,256,257]`.(并且欢迎修改文档的PR.) (2认同)
  • 尽管从实现来看似乎是这样,但“Apply”是一种情况而“Array”是另一种情况,这并不是真的。一种更简洁的思考方式是,“Array”可以被认为是“Apply”(和“Functor”等),但由于它们没有内置的“ap”方法,Ramda 提供了为他们准备的一份。Ramda 还为“Array”提供了适当的“map”函数,因为“Array.prototype”中的函数不遵循适当的规则。Ramda 在不同的地方也为“对象”和“函数”做了类似的事情。 (2认同)

Ber*_*rgi 7

lift/ liftN"将"普通函数"提升"为适用语境.

// lift1 :: (a -> b) -> f a -> f b
// lift1 :: (a -> b) -> [a] -> [b]
function lift1(fn) {
    return function(a_x) {
        return R.ap([fn], a_x);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在ap(f (a->b) -> f a -> f b)的类型也不容易理解,但列表示例应该是可以理解的.

这里有趣的是你传入一个列表并返回一个列表,所以你可以重复应用它,只要第一个列表中的函数具有正确的类型:

// lift2 :: (a -> b -> c) -> f a -> f b -> f c
// lift2 :: (a -> b -> c) -> [a] -> [b] -> [c]
function lift2(fn) {
    return function(a_x, a_y) {
        return R.ap(R.ap([fn], a_x), a_y);
    }
}
Run Code Online (Sandbox Code Playgroud)

而且lift3,你在你的例子中隐含使用的,它的工作方式相同 - 现在用ap(ap(ap([fn], a_x), a_y), a_z).

  • 我发现特定于arity的提升功能比Ramda的可变升力更加直观.Sanctuary提供[`lift`](http://sanctuary.js.org/#lift),[`lift2`](http://sanctuary.js.org/#lift2)和[`lift3`](http ://sanctuary.js.org/#lift3). (3认同)