简体   繁体   中英

Is Underscore.js functional programming a fake?

According to my understanding of functional programming, you should be able to chain multiple functions and then execute the whole chain by going through the input data once .

In other words, when I do the following (pseudo-code):

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

I expect that the list will be traversed once , when each value will be squared and then everything will be added up (hence, the map operation would be called as needed by the reduce operation).

However, when I look at the source code of Underscore.js, I see that all the "functional programming" functions actually produce intermediate collections like, for example, 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;
};

So the question is, as stated in the title, are we fooling ourselves that we do functional programming when we use Underscore.js?

What we actually do is make program look like functional programming without actually it being functional programming in fact. Imagine, I build a long chain of K filter() functions on list of length N, and then in Underscore.js my computational complexity will be O(K*N) instead of O(N) as would be expected in functional programming.

PS I've heard a lot about functional programming in JavaScript, and I was expecting to see some functions, generators, binding... Am I missing something?

Is Underscore.js functional programming a fake?

No, Underscore does have lots of useful functional helper functions. But yes, they're doing it wrong . You may want to have a look at Ramda instead.

I expect that the list will be traversed once

Yes, list will only be traversed once. It won't be mutated, it won't be held in memory (if you had not a variable reference to it). What reduce traverses is a different list, the one produced by map .

All the functions actually produce intermediate collections

Yes, that's the simplest way to implement this in a language like JavaScript. Many people rely on map executing all its callbacks before reduce is called, as they use side effects. JS does not enforce pure functions, and library authors don't want to confuse people.

Notice that even in pure languages like Haskell an intermediate structure is built 1 , though it would be consumed lazily so that it never is allocated as a whole.

There are libraries that implement this kind of optimisation in strict languages with the concept of transducers as known from Clojure. Examples in JS are transduce , transducers-js , transducers.js or underarm . Underscore and Ramda have been looking into them 2 too.

I was expecting to see some […] generators

Yes, generators/iterators that can be consumed lazily are another choice. You'll want to have a look at Lazy.js , highland , or immutable-js .

[1]: Well, not really - it's a too easy optimisation
[2]: https://github.com/jashkenas/underscore/issues/1896 , https://github.com/ramda/ramda/pull/865

Functional programming has nothing to do with traversing a sequence once; even Haskell, which is as pure as you're going to get, will traverse the length of a strict list twice if you ask it to filter pred (map fx) .

Functional programming is a simpler model of computation where the only things that are allowed to happen do not include side effects. For example, in Haskell basically only the following things are allowed to happen:

  1. You can apply a value f to another value x , producing a new value fx with no side-effects. The first value f is called a "function". It must be the case that any time you apply the same f to the same x you get the same answer for fx .
  2. You can give a name to a value, which might be a function or a simple value or whatever.
  3. You can define a new structure for data with a new type signature, and/or structure some data with those "constructors."
  4. You can define a new type-class or show how an existing data structure instantiates a type-class.
  5. You can "pattern match" a data structure, which is a combination of a case dispatch with naming the parts of the data structure for the rest of your project.

Notice how "print something to the console" is not doable in Haskell, nor is "alter an existing data structure." To print something to the console, you construct a value which represents the action of printing something to the console, and then give it a special name, main . (When you're compiling Haskell, you compute the action named main and then write it to disk as an executable program; when you run the program, that action is actually completed.) If there is already a main program, you figure out where you want to include the new action in the existing actions of that program, then use a function to sequence the console logging with the existing actions. The Haskell program never does anything; it just represents doing something.

That is the essence of functional programming. It is weaker than normal programming languages where the language itself does stuff, like JavaScript's console.log() function which immediately performs its side effect whenever the JS interpreter runs through it. In particular, there are some things which are (or seem to be) O(1) or O(log(log(n))) in normal programs where our best functional equivalent is O(log(n)).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM