Showing posts with label reduce(). Show all posts
Showing posts with label reduce(). Show all posts

Sunday, January 12, 2014

Improved map() & reduce() ?

In a previous post JavaScript function F () I presented the function F () which allows you to create functions from simple String expressions.

F() makes using JavaScript map() and other "functors"  more concise, allowing you to replace:
  [1,2,3].map(function (x){return x * 10});
with:
  [1,2,3].map(F('x * 10'));

But I and some commentators felt the above still looked a bit cryptic for common use.  Having to know what F() means is an extra mental load for readers of this code, as is the extra set of parentheses needed.  And F() needs to be more or less cryptic, because it is supposed to be a shorthand after all.

So,  here might be a better solution (but see the caveat at the end of this post):

1. Go to section titled "Polyfill" at MDN's Array.prototype.map. It gives the code to implement map() on older browsers which don't support that natively.

2. Read and be aware of the license(s) provided under which you can use their code, linked to at the bottom of their page.

3. Copy their code as instructed to your own script.

4. Replace the beginning of their code :

  if (!Array.prototype.map)
  { Array.prototype.map = function(fun /*, thisArg */)
  ...
  
with this:

  function F (aString)
  { return new Function("x", "y", "z", "w", "return " + aString);
  }

  Array.prototype.map 
  = function (funARG)
  {  var fun = funARG;
     if (typeof fun == "string")
     { fun = F(fun);
     }
   ...

The above replaces the native implementation of map() if it did exist with our enhanced version of MDN's polyfill. If it didn't, it creates the method map() for the Array prototype.

You can now replace:
  [1,2,3].map(F('x * 10'));

with the more simple:
  [1,2,3].map('x * 10');

You can do a similar enhancement to their reduce() -code, available on the same site. Then you can write:
  [1,2,3].reduce('x + y');  

Documentation of map() and reduce() and their polyfills can be found at  https://developer.mozilla.org/. Interestingly, I think the above makes the case that using a polyfill is sometimes better than having the native implementation (which theoretically can differ between browsers as well).

In general I think the point is that using functional expressions like map() should make programming simpler, not more verbose and complicated. It would be great if ECMA at some point added a shorthand syntax for anonymous functions to the JavaScript standard.  But, it can in fact be argued that the solution above is good just because it does not require any new syntax, just an 'overloaded' semantics for map() and other functors.


UPDATE / CAVEAT:  In fact I can not  recommend this solution for general use.  Depending on your application and the JavaScript libraries it uses, the solution above might break your application.. The reason is that when you add a method to the prototype of Array, it means that when looping over an array, that added property will on some browsers become one of the properties being looped over. That might break some existing code not only in your application but also in the libraries you use.  So, for me at least it is back to using the  function F(). Avoid adding your own methods to JavaScript built-in prototypes, at least things you can iterate over!


© 2014 Panu Viljamaa. This work is licensed under a Creative Commons Attribution 4.0 International License

Saturday, January 11, 2014

The JavaScript function F()

The JavaScript ECMA-262 standard 5th edition provides for Array-iterators like map() and reduce(). This means you don't need to write boiler-plate code for for -loops. Instead you can write things in a more functional way like this:
 [1,2,3].map(function (x){return x * 10});

What diminishes the utility of iterators like map() is that unlike some other languages, JavaScript does not have a shorthand syntax for anonymous functions.  You need to write a whole function-definition to call map(), as seen above.

So what could be done to reduce our workload and increase readability of our code even further? Answer:  Define function F() as follows:

  function F(aString)
  { var s, f2 ;
    s = 'f2 = function (x,y,z,w)'
      + '{return ' + aString + '}';
    eval(s);
    return f2;
  }

You can now make your code shorter like this:

  a =    [1,2,3].map(F('x * 10'));
  // vs. [1,2,3].map(function (x){return x * 10});
  ok (a[0] == 10);  
  ok (a[1] == 20);  
  ok (a[2] == 30);

  n =    [1,2,3,4].reduce(F('x + y'));
  // vs. [1,2,3,4].reduce(function (x,y){return x + y;});
  ok(n == 10);  


The function ok () used above is a simple way to express expected behavior of code in code. It can be defined simply as:

  function ok (arg)
  { if (!arg) {throw "ok() not ok"; }
  }

After publishing the first version of this post I got a suggestion from Georgiy Shibaev at Google+ JavaScript community to use the Function -constructor instead of eval.  And indeed it allows us to write the  function F() in a simpler way:

  function F (aString)
  { return new Function("x", "y", "z", "w", "return " + aString);
  }

Performance-wise there doesn't seem to be any difference between the two versions, tested on IE10. The Function -constructor still needs to do an eval internally, since you pass it a String argument.  I think this is a good use-case, and example how the Function-constructor is useful sometimes.

There are some caveats to using F ().  It can not create functions that would contain  if-then-else or other statements inside it. But you can use the ternary operator ? : .  If you need something more complicated then of course you can always write a full function definition.

Documentation of map() and reduce(),  including how to implement them on older browsers can be found at: https://developer.mozilla.org/

UPDATE:  My next blog-post Improved map() & reduce() improves this solution further.

UPDATE-2: In earlier version I called this function 'f()'.  But on 2nd thought this function is better named F (), to avoid possible conflicts with local variables, which typically at least start lowercase. I've made the change above


© 2014 Panu Viljamaa. This work is licensed under a Creative Commons Attribution 4.0 International License