Javascript Can';“别把我的头缠在周围”;“电梯”;在Ramda.js中
查看Ramda.js的源代码,特别是“lift”函数 下面是给定的示例:Javascript Can';“别把我的头缠在周围”;“电梯”;在Ramda.js中,javascript,functional-programming,ramda.js,lifting,Javascript,Functional Programming,Ramda.js,Lifting,查看Ramda.js的源代码,特别是“lift”函数 下面是给定的示例: 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] 因此结果的第一个数字很简单,a、b和c都是每个数组的第一个元素。第二个对我来说并不容易理解。参数是每个数组的第二个值(2,2,未定义),还是第一个数组的第二个值以及第二个和第
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]
因此结果的第一个数字很简单,a
、b
和c
都是每个数组的第一个元素。第二个对我来说并不容易理解。参数是每个数组的第二个值(2,2,未定义),还是第一个数组的第二个值以及第二个和第三个数组的第一个值
即使不考虑这里发生的事情的顺序,我也看不出它的价值。如果我在没有先提升它的情况下执行它,我将以数组
concat
作为字符串启用而结束。这看起来有点像flatMap
,但我似乎无法遵循其背后的逻辑 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);
}
}
现在(f(a->b)->fa->fb
)的类型也不容易理解,但是列表示例应该可以理解
这里有趣的是,您传入一个列表,然后返回一个列表,因此只要第一个列表中的函数具有正确的类型,您就可以重复应用它:
// 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);
}
}
而您在示例中隐式使用的
lift3
,也同样适用于ap(ap(ap([fn],a_x),a_y),a_z)
Bergi的答案非常好。但另一种思考方法是更具体一点。Ramda确实需要在其文档中包含一个非列表示例,因为列表并不能真正捕获这一点
让我们看一个简单的函数:
var add3 = (a, b, c) => a + b + c;
这对三个数字起作用。但是如果你的容器里有数字呢?也许我们有s。我们不能简单地将它们相加:
const Just = Maybe.Just, Nothing = Maybe.Nothing;
add3(Just(10), Just(15), Just(17)); //=> ERROR!
(好的,这是Javascript,它实际上不会在这里抛出错误,只是尝试连接它不应该连接的东西…但它肯定不会做你想要的!)
如果我们能将这种功能提升到容器的水平,我们的生活就会更轻松。Bergi指出的lift3
在Ramda中通过liftN(3,fn)
和一个glosslift(fn)
实现,该gloss仅使用所提供函数的算术性。因此,我们可以:
const madd3 = R.lift(add3);
madd3(Just(10), Just(15), Just(17)); //=> Just(42)
madd3(Just(10), Nothing(), Just(17)); //=> Nothing()
但是这个函数不知道关于容器的任何具体信息,只知道它们实现了ap
。Ramda为列表实现ap
,方法类似于将函数应用于列表叉积中的元组,因此我们也可以这样做:
madd3([100, 200], [30, 40], [5, 6, 7]);
//=> [135, 136, 137, 145, 146, 147, 235, 236, 237, 245, 246, 247]
这就是我对电梯的看法。它接受一个在某些值级别上工作的函数,并将其提升到在这些值的容器级别上工作的函数。多亏了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]
对我来说,在理解它之前,有三个问题需要回答
Apply
)以及Apply#ap
的作用Apply
spec的关系应用
规范
在FantasyLand中,当对象定义了ap
方法时,该对象实现spec(该对象还必须通过定义map
方法来实现spec)
ap
方法具有以下签名:
ap :: Apply f => f a ~> f (a -> b) -> f b
在:
声明类型约束,因此上面签名中的=>
指类型f
Apply
声明方法声明,因此~>
应该是在ap
上声明的函数,它围绕着一个值,我们称之为Apply
(我们将在下面的示例中看到,一些a
与此签名不一致,但想法是相同的)ap
v
和u
(v=fa;u=f(a->b)
),因此这个表达式是有效的v.ap(u)
,需要注意的是:
和v
都实现u
应用
持有一个值,v
持有一个函数,但它们具有与u
相同的“接口”(这将有助于理解下面关于Apply
和R.ap
数组的部分)
- 值
和函数a
不知道a->b
,函数只是转换值Apply
。是a
将值和函数放在容器中,是Apply
将它们提取出来,对值调用函数并将它们放回容器中ap
R.ap
签名有两种情况:
Apply f=>f(a→ (b)→ f a→ f b
:这与上一节中的Apply#ap
签名非常相似,区别在于调用ap
的方式(Apply#ap
与R.ap
)以及参数顺序[a]→ b]→ [a]→ [b]
:如果我们将Apply f
替换为Array
,这是一个版本,还记得上一节中的值和函数必须包装在同一个容器中吗?这就是为什么将R.ap
与Array
s一起使用时,第一个参数是函数列表,即使您只想应用一个函数,也要将其放入数组中Maybe
from,它实现了Apply
,这里的一个不一致之处是它的签名是:ap::Apply f=>f(a->b)~>fa->
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 }
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 ]
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 }
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]