将 R.filter 和 R.map 重构为 pointfree 风格的 R.reduce

Refactor R.filter and R.map to just R.reduce in pointfree style

我已经开始学习 Ramda.js 和函数式编程,并且对使用 pointfree 编程风格的函数式组合非常感兴趣,但是我很难理解其中的一些,我希望有人可以帮忙说明一下:

假设我有一个人员列表 - 我只想获取年龄在 13-19 岁(青少年)之间的人员。然后,我想将每个人映射到该人的 getName() 方法(如果存在)的 return 值,否则映射到他们的 name 属性。然后,我想在名字上调用.toUpperCase()

如果我使用常规的 JS 原型方法,我不会使用 Array.prototype.filter 来获取青少年用户然后 Array.prototype.map。出于性能原因,我会使用 Array.prototype.reduce,它的主体将由一个条件来保护,该条件检查每个迭代项目是否满足青少年的标准。这样,我就少了一次迭代。 Elijah Manor has an article about this on his blog.

这是我使用 R.filterR.map 想出的 pointfree Ramda 代码(按预期工作):

var people = [
  { name: 'Bob', gender: 'male', age: 22 },
  { name: 'Jones', gender: 'male', age: 15 },
  { name: 'Alice', gender: 'female', age: 19 },
  { name: 'Carol', gender: 'female', age: 32 },
  { name: 'Odu', gender: 'male', age: 25 },
  { name: 'Fred', gender: 'male', age: 55 },
  { name: 'Nicole', gender: 'female', age: 29 },
  { getName: function() { return 'David' }, gender: 'male', age: 23 }
]

var getUpcasedTeenagerNames = R.pipe(
  R.filter(
    R.propSatisfies(R.both(R.lte(13), R.gte(19)), 'age')
  ),
  R.map(
    R.pipe(
      R.ifElse(
        R.propIs(Function, 'getName'),
        R.invoker(0, 'getName'),
        R.prop('name')
      ),
      R.toUpper
    )
  )
)

getUpcasedTeenagerNames(people) // => ['JONES', 'ALICE']

我的问题是 - 我将如何重写上述算法的以下原生版本以使用 pointfree Ramda.js?

var getUpcasedTeenagerNames = function(people) {
  return people
    .reduce(function(teenagers, person) {
      var age = person.age
      if (age >= 13 && age <= 19) {
        var name
        if (typeof (name = person.getName) === 'function') {
          name = name()
        } else {
          name = person.name
        }
        teenagers.push(name.toUpperCase())
      }
      return teenagers
    }, [])
}

我试过用 R.scan 来做,看过 R.reducedR.when 但恐怕我可能漏掉了一点点。

为了您的方便,我在 Ramda REPL 中包含了这段代码:http://goo.gl/6hBi5k

首先,我会以不同的方式分解问题。我会使用 Ramda 的 R.__ 占位符来填充 R.lteR.gte 的第一个参数,以便它们更好地阅读。我喜欢用一个简单的下划线作为别名,所以这将显示为 R.both(R.gte(_, 13), R.lte(_, 19)),我发现它更具可读性。然后我会分离出在一个人身上找到名字的功能。这显然是 stand-alone 的一小段代码,将其取出可以使主要代码更具可读性。

最后,重要的是,如果您稍微了解 Transducers,您将学到一个技巧,无需担心中间集合,因为中间集合可能是初始技术中可能出现的性能问题.

var _ = R.__;
var findName = R.ifElse(
  R.propIs(Function, 'getName'),
  R.invoker(0, 'getName'),
  R.prop('name')
); 

var getUpcasedTeenagerNames = R.into([], R.compose(
  R.filter(
    R.propSatisfies(R.both(R.gte(_, 13), R.lte(_, 19)), 'age')
  ),
  R.map(R.pipe(findName, R.toUpper))
));

getUpcasedTeenagerNames(people); //=> ["JONES", "ALICE"]

现在我根本不会担心性能问题,除非我发现了性能问题并表明这部分代码是罪魁祸首。但如果我有,那么我可以通过使用换能器来修复它,并且由于 mapfilter 都已经工作了,我需要做的就是切换我的合成方向(这里通过从管道改变作曲)并用 into([]).

包裹起来

如果您有兴趣,这里 an article on user transducers in Ramda, and another good intro 给传感器。

如果我有一点时间,我会看看是否可以将您的代码转换为 points-free 解决方案,但请不要迷信 points-free。这是一个有用的 技术在某些情况下,但我们不应该被迫在它不容易适应的地方使用它。