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:
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 -------------------------
// ------------------ 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()
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.
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.
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