Ramda — 提取两个属性并将一个附加到另一个 pointfree 样式

Ramda — extract two properties and append one to the other pointfree style

我有一些数据的形式:

const data = {
    list: [1, 2, 3],
    newItem: 5
}

我想创建一个函数,将 newItem 的值追加到 list 上,从而得到这个新版本的 data:

{
list: [1,2,3,5],
newItem: 5,
}

(最终,我会在将 newItem 移入列表后将其删除,但我正在尝试简化此问题的问题)。

我正在尝试使用 pointfree 风格和 Ramda.js 作为学习经验。

这是我目前的位置:

const addItem = R.assoc('list',
    R.pipe(
        R.prop('list'),
        R.append(R.prop('newItem'))
    )
)

的想法是生成一个接受data的函数,但在这个例子中,对R.append的调用也需要对数据的引用,我试图避免明确提及[=15] =] 为了保持 Pointfree 风格。

这可以不提 data 吗?

如果我没理解错的话,您想从 {x:3, y:[1,2]} 转到 [1,2,3]。这是一种方法:

const fn = compose(apply(append), props(['x', 'y']))

fn({x:3, y:[1,2]});
//=> [1,2,3]

const addItem = R.chain
  ( R.assoc('list') )
  ( R.converge(R.append, [R.prop('newItem'), R.prop('list')]) );

const data = {
    list: [1, 2, 3],
    newItem: 5
};

console.log(addItem(data));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script>

原因如下:

首先我们可以看看当前 addItem 在没有点自由时应该是什么样子:

const addItem = x => R.assoc('list')
  (
    R.pipe(
        R.prop('list'),
        R.append(R.prop('newItem')(x))
    )(x)
  )(x);

console.log(addItem({ list: [1, 2, 3], newItem: 5 }));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script>

它需要一些数据并在三个地方使用它。我们可以重构一下:

const f = R.assoc('list');
const g = x => R.pipe(
        R.prop('list'),
        R.append(R.prop('newItem')(x))
    )(x)

const addItem = x => f(g(x))(x);

console.log(addItem({ list: [1, 2, 3], newItem: 5 }));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script>

x => f(g(x))(x) 部分可能不是很明显,但查看 list of common combinators in JavaScript 可以识别为 S_:

Name # Haskell Ramda Sanctuary Signature
chain S_³ (=<<)² chain² chain² (a → b → c) → (b → a) → b → c

因此x => f(g(x))(x)可以简化为R.chain(f)(g).


这留下了 g,它仍然采用一个参数并在两个地方使用它。最终目标是从一个对象中提取两个属性并将它们传递给 R.append(),这可以更容易地(和 pointfree)用 R.converge() 表示为:

const g = R.converge(R.append, [R.prop('newItem'), R.prop('list')]);

fg 替换回

const addItem = R.chain
     ( R.assoc('list') )
     ( R.converge(R.append, [R.prop('newItem'), R.prop('list')]) );

正如关于 customcommander 的回答的讨论所示,有两种不同的可能解释。

如果你只想接收[1, 2, 3, 5],那么你可以像customcommander那样做,或者我会选择的方式:

const fn1 = lift (append) (prop ('newItem'), prop ('list'))

但是如果你想要像 {list: [1, 2, 3, 5], newItem: 5} 这样的东西,那么你可以在 applySpec 中使用上面的内容并将其与合并结合起来,如下所示:

const fn2 = chain (mergeLeft, applySpec ({list: fn1}))

这是一个片段:

const fn1 = lift (append) (prop ('newItem'), prop ('list')) 
const fn2 = chain (mergeLeft, applySpec ({list: fn1}))


const data = {list: [1, 2, 3], newItem: 5}

console .log (fn1 (data)) //=> [1, 2, 3, 5]
console .log (fn2 (data)) //=> {list: [1, 2, 3, 5], newItem: 5}
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script>
<script> const {lift, append, prop, chain, mergeLeft, applySpec} = R </script>

第二个有点笨拙,一旦你内联 fn1。它在两个地方重复 属性 list,这总是困扰着我。但是暂时没有好的解决办法

我有好几次想要 R.evolveR.applySpec 的组合,它像 evolve 一样在外部工作,让您只指定需要更改的属性,但是其转换函数被赋予整个输入对象,而不仅仅是相应的 属性.

有了类似的东西,这可能看起来像

const f3 = evolveSpec ({
  list: ({list, newItem}) => [...list, newItem]
})

或使用上面的:

const f3 = evolveSpec ({
  list: lift (append) (prop ('newItem'), prop ('list'))
})

我认为这可能是包含在 Ramda 中的有用候选者。