dip*_*rus 22 javascript functional-programming lifting ramda.js
查看Ramda.js的来源,特别是"升力"功能.
这是给定的例子:
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.它需要一个在某些值的级别上工作的函数,并将其提升到在这些值的容器级别上工作的函数.
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)
对我来说,在理解之前有三个问题需要回答.
Apply规范(我将其称为Apply)以及Apply#ap它的作用R.ap实现以及Array与Apply规范有什么关系R.liftApply规范在fantasy-land中,对象Apply在ap定义方法时实现规范(该对象还必须Functor通过定义map方法来实现规范).
该ap方法具有以下特征:
ap :: Apply f => f a ~> f (a -> b) -> f b
Run Code Online (Sandbox Code Playgroud)
=>声明类型约束,所以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和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)和params的顺序.[a ? b] ? [a] ? [b]:这是如果更换版本Apply f有Array,请记住,价值和功能必须被包裹在上一节中相同的容器?这就是为什么当使用R.ap带有ArrayS,第一个参数是函数的列表,即使你想申请只有一个功能,把它放在一个阵列.让我们来看一个例子,我使用的是Maybe从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 }
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)
希望这可以帮助.
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).
| 归档时间: |
|
| 查看次数: |
3679 次 |
| 最近记录: |