Wednesday, January 11, 2017

Function.prototype.map II


1.  PREVIOUSLY

In the previous blog-post titled  Function.prototype.map  I proposed  that Functions in JavaScript should have  the method "map()" like Arrays do, allowing you to process each element with a function instead of the longer and more complicated for-in -statement.

Array.prototype.map()  is called like:   myArray.map(myFunction). The proposed Function.prototype.map:     myFunction.map(myArray).

Apart from the syntactic difference of reversing the roles of argument and recipient, Function.prototype.map also removes all undefined elements from the result. That makes it suitable for not only transforming every element of an array, but also for filtering out some of them.

In this  follow-up I present one more feature differentiating Function.prototype.map from Array-prototype.map: Its ability to iterate over non-Array Objects.

 

2. EXAMPLE II  

The new behavior in version-2 is exemplified by the following few lines of code The function ok() is my simple ad hoc assert-utility which  throws an error if called with anything untrue.
  
 var o  = {x:1, y:2, z:3};
 var o2 = odd.map (o);

 ok (o2.x === 1         );
 ok (o2.y === undefined );
 ok (o2.z === 3         );

 function odd (n)
 { if (n % 2) {return n}
 }

 function ok (b)
 { if (!b)  
   { throw "assert failed";
   }
 }


5. MOTIVATING EXAMPLE

You might write a function that takes as argument an object with fields x, y, z whose values must be numbers, representing a point in three-dimensional space. You then might want to write a short routine that checks that x, y and z really are numbers, all of them. 

Assuming your argument is named 'coordinates' you could write checker-code like:

  // var coordinates = {x:1, y:2, z:3};
  for (var p in  coordinates)
  { assertIsNumber (coordinates, p); 
  }

But aren't you tired of writing for-loops already?  

Having Function.prototype.map() installed, you can write the equivalent of the above as:

  // var coordinates = {x:1, y:2, z:3};
  assertIsNumber.map (coordinates);


In both cases the checker-function could be defined as:

function assertIsNumber (n)
{  if (typeof n === "number") return;
   throw "Not a number: " + n;
}


3. IMPLEMENTATION

Below you see the JavaScript code for installation, tests, and implementation of this new improved version:

// ------------------  cut here -------------------------

// 1. INSTALLATION:
Function.prototype.map = map2;

// 2. TESTS:
test_map2 (); 

function test_map2 ()
{
 // 2.A:  FOR ARRAYS:
 var a  = [1,2,3];
 var a2 = odd.map(a);

 ok (a2[0] === 1);
 ok (a2[1] === 3);
 ok (a2.length  === 2);


 // 2.B:  FOR OBJECTS:
 var o  = {x:1, y:2, z:3};
 var o2 = odd.map (o);

 ok (o2.x === 1        );
 ok (o2.y === undefined);
 ok (o2.z === 3        );


 // 2.C: MOTIVATING EXAMPLE
 var coordinates =  {x:1, y:2, z:3};
 assertIsNumber.map (coordinates);

 // Next would fail because y-value is not a number:
 // assertIsNumber.map ({x:1, y:"s"} );

 return;  // from test_map2()

 function ok (b)
 { if (!b)  
   { throw "assert failed";
   }
 }

 function assertIsNumber (n)
 { if (typeof n === "number") 
{ return n;
}
   throw "not a number: " + n;
 }
}


// 3. THE IMPLEMENTATION:

function map2 (objectOrArray, thisArg)
{  
  var resultA, resultB;
  if (objectOrArray.constructor === Array)
  { return map2_array.call (this, objectOrArray, thisArg);
  }
  return  map2_object.call (this, objectOrArray, thisArg);


    function map2_object (anObject, thisArg)
    { var result  = new anObject.constructor(); 
      for (var p in anObject)
      { var v = this (anObject[p], p, anObject) ;
        if (v !== undefined)
        { result  [p] = v;
        }
      }
      return result;
    } // end map2_object()

    function map2_array (anArray, thisArg)
    { resultA = [].map.call  (anArray, this, thisArg);
      resultB = [];
      for (var j=0; j < resultA.length; j++)
      { var e = resultA[j];
        if (e !== undefined)
{ resultB . push(e);  
        }
}
return resultB;
     } // end map2_array()

   } // end map2()

// ------------------  cut here -------------------------


4. HOW TO USE IT 

Copy the code from Section 3. to the start of your JavaScript-file. After that you can call  'map()' on any function with any []  OR any {} as argument.

What name to use to install this function as a method in Function.prototype is of course up to you. In my own projects I've chosen to install it as 'map' because of  its close proximity in  behavior to the already standard Array.prototype.map.


5. DISCUSSION

Iterating over non-arrays is a frequent task in JavaScript. The for-in statement which does it however quickly becomes tedious to write, again and again. JavaScript does have the Array.prototype.map() which makes it simpler to iterate over Arrays.

Array.prototype.map can not be used for non-arrays because it is not Object.prototype.map! But with Function.prototype.map you can perform the more general task of calling map() on a Function, to process Objects and Arrays alike.

Yes, we are aware there is also Map.prototype.map, but that is not quite the same as Function.prototype.map. For one thing it doesn't remove undefined values and it only works on instances of Map. And it returns undefined.

For more about Function.prototype.map, see my previous blog-post Function.prototype.map.

_____________________________________________________________
Copyright © 2017 Panu Viljamaa. All rights reserved unless otherwise noted.
Reuse of source-code in this blog-post is allowed under the terms of 
Creative Commons Attribution 4.0 International (CC BY 4.0) -license 

No comments:

Post a Comment