Underscore.js函数式编程是假的吗?

Is Underscore.js functional programming a fake?

根据我对函数式编程的理解,您应该能够链接多个函数,然后通过遍历一次输入数据来执行整个链.

换句话说,当我执行以下操作时 (pseudo-code):

list = [1, 2, 3];
sum_squares = list
   .map(function(item) { return item * item; })
   .reduce(function(total, item) { return total + item; }, 0);

我希望列表会被遍历一次,这时每个值都会被平方,然后所有的东西都会被加起来(因此,地图操作将根据需要被调用减少操作)。

但是,当我查看 Underscore.js 的源代码时,我发现所有 "functional programming" 函数实际上都生成中间 collections ,例如,so:

// Return the results of applying the iteratee to each element.
_.map = _.collect = function(obj, iteratee, context) {
  iteratee = cb(iteratee, context);
  var keys = !isArrayLike(obj) && _.keys(obj),
      length = (keys || obj).length,
      results = Array(length);
  for (var index = 0; index < length; index++) {
    var currentKey = keys ? keys[index] : index;
    results[index] = iteratee(obj[currentKey], currentKey, obj);
  }
  return results;
};

那么问题来了,正如标题所说,我们在使用Underscore.js的时候,是在自欺欺人的说我们在做函数式编程吗?

我们实际上做的是让程序看起来像函数式编程,而不是实际上它实际上是函数式编程。想象一下,我在长度为 N 的列表上构建了一个长长的 K filter() 函数链,然后在 Underscore.js 中,我的计算复杂度将是 O(K*N) 而不是函数中预期的 O(N)编程。

P.S。我在 JavaScript 中听说过很多关于函数式编程的内容,我期待看到一些函数、生成器、绑定...我是否遗漏了什么?

函数式编程与遍历一次序列无关;即使是 Haskell,它和你将要得到的一样纯粹,如果你要求它达到 filter pred (map f x),它也会遍历严格列表的长度两次。

函数式编程是一种更简单的计算模型,其中唯一允许发生的事情不包括副作用。例如,在 Haskell 中基本上只允许发生以下事情:

  1. 您可以将一个值 f 应用到另一个值 x,生成一个没有副作用的新值 f x。第一个值 f 称为 "function"。一定是这样的情况,任何时候你将相同的 f 应用于相同的 x 你会得到相同的答案 f x.
  2. 您可以为一个值命名,它可以是一个函数或一个简单的值或其他任何东西。
  3. 您可以为具有新类型签名的数据定义新结构,and/or 使用那些 "constructors."
  4. 构造一些数据
  5. 您可以定义一个新类型-class 或显示现有数据结构如何实例化一个类型-class。
  6. 您可以 "pattern match" 数据结构,它是案例分派与为项目的其余部分命名数据结构部分的组合。

注意 "print something to the console" 在 Haskell 中是不可行的,"alter an existing data structure." 也不可行 要将某些内容打印到控制台,您构造一个值来表示将某些内容打印到控制台的操作,然后给它起一个特殊的名字,main。 (当您编译 Haskell 时,您计算名为 main 的操作,然后将其作为可执行程序写入磁盘;当您 运行 程序时,该操作实际上已完成。)如果已经有一个 main 程序,您可以确定要在该程序的现有操作中包含新操作的位置,然后使用函数对控制台日志记录与现有操作进行排序。 Haskell 程序从不 任何事情;它只是代表在做某事。

这就是函数式编程的本质。它比语言本身做事的普通编程语言要弱,比如 JavaScript 的 console.log() 函数,只要 JS 解释器 运行 通过它就会立即执行它的副作用。特别是,有些东西在普通程序中是(或看起来是)O(1) 或 O(log(log(n))),而我们最好的功能等价物是 O(log(n)).

Is Underscore.js functional programming a fake?

不,Underscore 确实有很多有用的辅助函数。但是,是的,they're doing it wrong. You may want to have a look at Ramda 相反。

I expect that the list will be traversed once

是的,list只会遍历一次。它不会被改变,它不会被保存在内存中(如果你没有对它的变量引用)。 reduce 遍历的是一个不同的列表,由 map.

生成的列表

All the functions actually produce intermediate collections

是的,这是用像 JavaScript 这样的语言实现它的最简单方法。许多人依赖 map 在调用 reduce 之前执行其所有回调,因为他们使用副作用。 JS 不强制执行纯函数,库作者不想混淆人们。

请注意,即使在像 Haskell 这样的纯语言中,也会构建一个中间结构 1,尽管它会被延迟使用,因此它永远不会作为一个整体进行分配。

有些库以transducers as known from Clojure. Examples in JS are transduce, transducers-js, transducers.js or underarm的概念在严格的语言中实现了这种优化。 Underscore 和 Ramda 也一直在研究它们2

I was expecting to see some […] generators

是的,generators/iterators可以偷懒的也是一种选择。你会想看看 Lazy.js, highland, or immutable-js.

[1]:好吧,not really - 太容易优化了
[2]: https://github.com/jashkenas/underscore/issues/1896, https://github.com/ramda/ramda/pull/865