【问题标题】:Point-free composition of lenses in Ramda.jsRamda.js 中镜头的无点合成
【发布时间】:2019-01-16 02:56:51
【问题描述】:

我正在尝试编写返回镜头的函数,以生产新镜头,并以无点风格进行。

这可能是一个关于函数组合的更普遍的问题。镜头只是一个案例研究。我对镜头不感兴趣,但我想知道如何以无点方式组合这些功能的一般模式。

const obj = {a: {x: 0}, b: {x: 42}};

// this won't work, but I want it to work
const pointFreeComposedLens = R.compose(R.lensProp, R.lensProp('x'));
R.view(pointFreeComposedLens('a'), obj); // returns 'undefined'

// this works
const pointyComposedLens = key => R.compose(R.lensProp(key), R.lensProp('x'));
R.view(pointyComposedLens('a'), obj); // returns '0'

组合函数的模式是什么,这样我就不需要为组合管道中的第一个函数重写参数?

举一个令人震惊的例子:

const deepLens = (a, b, c) => R.lensPath([a, b, c]);

// This works, but is tedious & verbose
const extraDeep = (a, b, c, x) => R.compose(deepLens(a,b,c), R.lensProp(x));
const gammaDeep = (a, b, c, y) => R.compose(deepLens(a,b,c), R.lensProp(y));

// Doesn't work, but it would be nicer to write:
const extraDeep = x => R.compose(deepLens, R.lensProp(x));

// and call it like so:
R.view(extraDeep('a','b','c','x'), obj);

【问题讨论】:

  • 我认为镜头是开始学习构图的错误类型,因为它们可以说代表“高阶构图”,这意味着特定于镜头的构图(viewsetover ),它采用另一个组合来编码您要查找的路径。

标签: javascript functional-programming ramda.js


【解决方案1】:

我知道您只是将镜头视为示例,但这是一种获得我认为您希望从镜头中获得的行为的方法。

const {lensPath, compose, lens, view} = R

const deepLens = (a, b, c) => lensPath([a, b, c]);
const deeper = (lens, ...args) => compose(lens, lensPath(args))

const cLens = deepLens('a', 'b', 'c')
const obj =  {a: {b: { c: {d: 1, e: 2, f: {g: 3, h: 4, i: {j: 5, k: 6}}}}}}

console.log(view(cLens, obj)) //=> {d: 1, e: 2, f: {g: 3, h: 4, i: {j: 5, k: 6}}}
console.log(view(deeper(cLens, 'f', 'g'), obj)) //=> 3

const fLens = deeper(cLens, 'f')

console.log(view (fLens, obj)) //=> {g: 3, h: 4, i: {j: 5, k: 6}}

const jLens = deeper(cLens, 'f', 'i', 'j')
// or jLens = deeper(fLens, 'i', 'j')

console.log(view(jLens, obj)) //=> 5
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>

至于更广泛的构图问题,镜头通常是像 Ramda 这样的库的特殊情况,因为构图的顺序与通常预期的相反。 (技术原因太多了,这里就不多说了。)

但这就是为什么这不起作用:

const extraDeep = x => R.compose(deepLens, R.lensProp(x));

Ramda 确实允许合成链中的第一个函数(compose 中的最右边,pipe 中的最左边)接收附加参数。但是当合成顺序与镜头合成相反时,它不会做你可能喜欢的事情.

因此,如果您在其他上下文中遇到类似的作文问题,请打开一个单独的问题。我很想知道您在寻找什么。

【讨论】:

  • 接受!根据您对“这不起作用”的原因的解释。我就是不能有我的无点构图。我现在看到,实际上,我想将“额外”参数应用于compose 中的第一个参数。我想要这个:compose(add, negate)(1, 1) 等同于这个:(x => compose(add(x), negate))(1)(1)(返回 0)。但是,太糟糕了,这行不通。
  • 好吧,您可以通过对结果函数进行柯里化来获得接近该结果的东西。但这并不准确。 curry(compose(add, negate))(3)(8) //=> 5。那是因为它非常接近a => b => add(negate(a))(b)
  • Ramda 讨论过——并短暂尝试过——自动柯里化composepipe 的结果,但由于问题#1260#1318 和@987654323 中讨论的原因而放弃了它@
【解决方案2】:

Rest 参数会将代码缩短为:

 const extraDeep = (...rest) => last => R.compose(deepLens(...rest), R.lensProp(last))(rest.pop());

但我不确定这是否真的很优雅。

【讨论】:

    【解决方案3】:

    如果您的意图是编写一个接受路径和对象的函数, 那么path已经存在:

    R.path(['a', 'b'], {a: {b: 10}}); //=> 10
    

    如果您有兴趣删除某些函数中的某些参数,deepLens 可以重写如下:

    const deepLens = R.unapply(R.lensPath);
    

    这个免点版本的另一个好处是它不仅限于三个参数。它适用于任意数量的参数:

    deepLens('a', 'b');           //=> R.lensPath(['a', 'b']);
    deepLens('a', 'b', 'c');      //=> R.lensPath(['a', 'b', 'c']);
    deepLens('a', 'b', 'c', 'd'); //=> R.lensPath(['a', 'b', 'c', 'd']);
    

    【讨论】:

      猜你喜欢
      • 2019-11-28
      • 2016-08-02
      • 1970-01-01
      • 2021-05-05
      • 1970-01-01
      • 1970-01-01
      • 2016-09-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多