如何使用Ramda实现点无递归来删除对象中的null值?

12

我正在学习有关无点函数的知识,并尝试以该风格实现此递归空值移除器。

它能够工作,但不是无点函数:


function removeNulls(obj) {
  return R.ifElse(
    R.either(R.is(Array), R.is(Object)),
    R.pipe(
      R.filter(R.pipe(R.isNil, R.not)),
      R.map(removeNulls)
    ),
    R.identity
  )(obj)
}

module.exports = removeNulls
下面是我未能正常运作的尝试:
const removeNulls = R.ifElse(
  R.either(R.is(Array), R.is(Object)),
  R.pipe(
    R.filter(R.pipe(R.isNil, R.not)),
    // throws `ReferenceError: removeNulls is not defined`
    R.map(removeNulls)
  ),
  R.identity
)

1
你不能这样做,因为JavaScript没有提供一种懒惰声明参数的方式。你可能会尝试使用Y组合器,但那会变得非常丑陋。 - Bergi
@Bergi 感谢你的提示,我会保持原样。我很想学习更多关于使用 Y 组合器的知识,即使是为了将来使用。看起来 Ramda 缺少 Y 组合器,但我不知道该怎么做。这有点难以搜索... - Henry Marshall
@Bergi 是正确的,你不能用一个 const 来实现它... 但是如果你将动作(action)与应用分开,你就可以这样做:const filterNotNull = filter(pipe(isNil, not))const recurseAction = action => ifElse( either(is(Array), is(Object)), pipe( action, map(action) ), identity ) - Nigel Benns
@NigelBenns,你的recurseAction不是pointfree的,而且它也不是递归的——应该是map(recurseAction(action)) - Bergi
在JS中实现Y组合子的版本相当容易。但这并不适用于任何实际情况。 - Scott Sauyet
很多人在理解基本的函数概念之前就推崇无点风格编程。哎,我的心痛啊。 - Mulan
2个回答

23

幸运的是,JavaScript有处理它缺乏惰性的资源。 因此,完全可以通过使用lambda函数声明递归无点解决方案来解决,方法如下:a => f(a)。 只需将R.map(removeNull)替换为R.map(a => removeNull(a))

const removeNulls = R.ifElse(
    R.either(R.is(Array), R.is(Object)),
    R.pipe(
        R.filter(R.pipe(R.isNil, R.not)),
        R.map(a => removeNulls(a))
    ),
    R.identity
)

针对您的情况,我建议您使用R.reject,它是R.filter的反义词。由于您否定了谓词,所以R.filter(R.pipe(R.isNil, R.not))等同于R.reject(R.isNil)

const removeNulls = R.ifElse(
    R.either(R.is(Array), R.is(Object)),
    R.pipe(
        R.reject(R.isNil),
        R.map(a => removeNulls(a))
    ),
    R.identity
)

最后,这个函数的结构如下:ifElse(predicate, whenTrue, identity)等同于 when(predicate, whenTrue)

const removeNulls = R.when(
    R.either(R.is(Array), R.is(Object)),
    R.pipe(
        R.reject(R.isNil),
        R.map(a => removeNulls(a))
    )
)

针对Declan Whelan的评论,简化版如下,由于数组是对象

const removeNulls = R.when(
    R.is(Object),
    R.pipe(
        R.reject(R.isNil),
        R.map(a => removeNulls(a))
    )
)

一个小的简化是可能的,因为Array是一个Object:const removeNulls = R.when( R.is(Object), R.pipe( R.reject(R.isNil), R.map(a => removeNulls(a)) ) ) - Declan Whelan
但这并不是Pointfree的,OP的主要要求还没有被满足。优化得很好。:) - jlouzado
@jlouzado 是的,它是。 - yosbel
@yosbel,地图中的“a”是一个点。如果你只使用R.map(removeNulls),它不会起作用。 - jlouzado
@jlouzado,这是语言的限制,答案与语言允许的一样简洁。你有什么建议? - yosbel
@yosbel,“point-free”的定义不特定于编程语言。由于JavaScript没有本地支持,因此更完整的答案应该是基于Y组合器的方法,如评论中所提到的那样。我同意您的答案尽可能地使用了“point-free”。 - jlouzado

3

Javascript缺乏惰性,这是个双重杀手:当你在相同的作用域中调用const时,它无法解析定义中的resolveNulls。

此外,你不能只是使用map(recurseAction(action)),因为定义本身会导致堆栈溢出,所以你需要将其包装在另一个作用域中:

const {ifElse, always, tap, apply, either, is, isNil, not, pipe, filter, map, identity} = require('ramda')

const filterNotNull = filter(pipe(isNil, not))
const log = tap(console.log)

const recurseAction =
  action =>
    ifElse(
      either(is(Array), is(Object)),
      pipe(
        action,
        map(a => recurseAction(action)(a))
      ),
      identity
    )

const removeNulls = recurseAction(filterNotNull)

const a = {
  a: null,
  b: "blah",
  c: 2,
  d: undefined,
  e: {
    meow: null,
    blah: undefined,
    jim: 'bob'
  }
}

const b = removeNulls(a)
console.log(b)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接