使用不同的镜头依次将多个功能应用于对象

Sequentially apply multiple functions to object using different lenses

我想对对象中的数组执行一些更新,然后根据此更新计算另一个参数。这是我试过的:

import * as R from 'ramda'

const obj = {
    arr: [
        2,
        3
    ],
    result: {
        sumOfDoubled: 0
    }
};

const double = a => {
    return a*2;
}

const arrLens = R.lensProp('arr');
const res0sumOfDblLens = R.lensPath(['result','sumOfDoubled']);

const calc = R.pipe(
    R.over(arrLens,R.map(double)),
    R.view(arrLens),
    R.sum,
    R.set(res0sumOfDblLens)
);

const updatedObjA = calc(obj);
const updatedObjB = R.set(res0sumOfDblLens,R.sum(R.view(arrLens,R.over(arrLens,R.map(double),obj))),obj);


// what I want: {"arr":[4,6],"result":{"sumOfDoubled":10}}
console.log(JSON.stringify(obj)); //{"arr":[2,3],"result":{"sumOfDoubled":0}}, as expected
console.log(JSON.stringify(updatedObjA)); //undefined
console.log(JSON.stringify(updatedObjB)); //{"arr":[2,3],"result":{"sumOfDoubled":10}}, correct result but the array did not update

我意识到这两种方法都行不通;方法 A 归结为 R.set(res0sumOfDblLens,10),这没有意义,因为它没有操作的目标对象。另一方面,方法 B 对基础对象进行两次操作,而不是将第一次操作的结果作为第二次操作的输入传递。

我怎样才能只使用一个函数组合来实现这个?即,将 double() 函数应用于对象的一部分,然后将更新后的对象作为输入传递给计算 sumOfDoubled?

要获取更新后的值,和对象,所以你可以设置新的总和,你可以使用R.converge():

const arrLens = R.lensProp('arr');
const res0sumOfDblLens = R.lensPath(['result', 'sumOfDoubled']);

const calc = R.pipe(
  R.over(arrLens, R.map(R.multiply(2))),
  R.converge(R.set(res0sumOfDblLens), [
    R.pipe(R.view(arrLens), R.sum), 
    R.identity
  ])
);

const obj = { arr: [2, 3], result: { sumOfDoubled: 0 }};

const result = calc(obj);

console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>

以及 OriDrori 的 converge solution, you could also use either of two other Ramda functions. I always prefer liftconverge 当它工作时;它感觉更像是标准的 FP,其中 converge 非常像是 Ramda 的神器。由于 converge 的一些可变特性,它并不总是能完成这项工作。但它在这里,你可以写:

const calc = pipe (
  over (arrLens, map (multiply (2))),
  lift (set (res0sumOfDblLens) ) ( 
    pipe (view (arrLens), sum),
    identity
  )
)

但是 identity in either of these solutions makes me wonder if there's something better. And there is. Ramda's chain 应用于函数时有时被称为 Starling 组合器 :: (a -> b -> c) -> (a -> b) -> a -> c。或者换一种说法,chain (f, g) //~> (x) => f (g (x)) (x)。这正是我们想要在这里应用的。所以有了 chain,这进一步简化了:

const arrLens = lensProp('arr')
const res0sumOfDblLens = lensPath(['result', 'sumOfDoubled'])

const calc = pipe (
  over (arrLens, map (multiply (2))),
  chain (
    set (res0sumOfDblLens), 
    pipe (view (arrLens), sum)
  )
) 

const obj = { arr: [2, 3], result: { sumOfDoubled: 0 }}

console .log (calc (obj))
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>const {lensProp, lensPath, pipe, over, map, multiply, chain, set, view, sum} = R </script>

也许没有镜头的变体更适合您的情况?

const doubleArr = pipe(
    path(['arr']),
    map(x => x*2)
  )
const newData = applySpec({
  arr: doubleArr,
  result: {
    sumOfDoubled: pipe(
       doubleArr,
       sum
    )
  }
})