If you want to catch up with the previous episodes, they are Function.prototype.map and Function.prototype.map II.
1. WHAT MORE CAN THERE BE?
So Function.prototype.map can take as argument either an Array OR any (non-Array) object, and knows how to handle them appropriately. This raises the question are there any other argument-types it could beneficially handle in a similar or at least somewhat coherent manner? What if we gave it a Function as an argument? What should that do? That is the subject of this 3rd blog-post in this series, which I aim to make short, not a double-album ...We can look for answer on what Function.prototype.map() should do when given a Function argument by looking at the type-signatures of the two previously provided extensions:
- Function.prototype.map (Array) -> Array
- Function.prototype.map (Object) -> Object
- Function.prototype.map (Function) -> Function
I think it is clear that should return a function, to preserve commonality with the other two cases. But what function? Notice this should be a method of any function, taking as argument some other function. What should the resulting function do, in general? What's the most basic way to use two existing functions to produce a third function as a result?
To make the question more concrete, if
funkA . map (funkB) -> funkC
then how should we produce funkC from funkA and funkB?
The best answer I believe is "Function Composition". The argument to funkC is first passed to funkA, whose result is then passed as argument to funkB, whose result is then returned finally as the result of funkC.
Note that this puts some constraint on the type signatures of functions funkA and funkB to be combined: It must be possible to call the second function funkB with the result of the first function funkA. We need to be aware of this constraint when we go about our jolly ways of combining functions.
However there is one thing we can do to make different functions more "recombinable": Allow a function to return undefined IF it does not know how to handle its argument - OR if it more simply wants to state the answer is undefined. For instance if you try to divide something by zero it makes a lot of sense to say the answer is undefined.
If we allow for undefined results then the combination rule can be amended as follows:
IF the first function funkA returns undefined, the second function funkB is NOT called with it and the result of funkC will be undefined as well.
2. EXAMPLE III
The first example below shows how it is possible to "map a function to itself". This is possible IF the argument-type and result-type of the function are the same:var times8 = double.map(double)
.map(double);
ok (times8 (1) === 8 );
ok (times8 (2) === 16);
The function double() expects a number as argument and returns a number that is double the argument, so this is clearly the case here. And clearly there is no limit as to how many times we can combine such a function with itself. The external inputs just keeps traveling though the "pipeline" created by the combination, doubling in value at each stage. You can think of the above example as there "amplifiers" connected in series to produce and 8-fold increase in the volume of our album "Function.prototype.map III".
The second example shows how we can take advantage of the "Do not pass undefined information on" -amendment we gave to our function-composition rule.
Function "odd()" returns its argument number if it is odd, else it returns undefined. If we combine it with the function double() we get double the argument for every odd number, and undefined for every even number given as argument. From that the following ensues:
var doubleOdds = odd.map(double);
ok (doubleOdds(0) === undefined);
ok (doubleOdds(1) === 2);
ok (doubleOdds(2) === undefined);
ok (doubleOdds(3) === 6);
function odd (n)
{ if (n % 2) {return n}
}
function double (n)
{ return n * 2;
}
function odd (n)
{ if (n % 2) {return n}
}
function even (n)
{ if (n % 2 === 0) { return n;}
}
function ok (b)
{ if (!b)
{ throw "assert failed";
}
}
3. IMPLEMENTATION
The full source-code and tests for Function.prototype.map() which implements the examples in this and previous blog-posts is available at GitHub and also at the Node.js npm -repository. The latter means that if you have Node.js installed you can get the code simply by executing:- npm install fpmap
5. DISCUSSION
So what have we now? We have a function you can use wherever you use functions, to transform and filter both Arrays and Objects. And by passing in a function as argument you can now do also "function composition".To avoid any ambiguity, let's refer to the implementation of Function.prototype.map discussed in this and previous blog-posts as "fpmap". Since that name now exists in the global npm-registry, it's a fairly good unique name for it.
One potential issue you might have against using fpmap is that it requires an addition to the JavaScript built-in object Function.prototype. This is potentially problematic if multiple sources provide similar additions to the same built-in objects of JavaScript. Something named Function.prototype.map might even be part of the EcmaScript -standard one day, who knows.
To make potential name-conflicts less of a problem, our implementation "fpmap" will cause an error if you try to install it but it detects that Function.prototype already has the property "map" . You will then see an error so you will immediately know there's a conflict.
What you can do then is install "fpmap" under a different name, say "map2". We propose other writers of base-object extensions should follow the same convention: Never assign a property to built-in JavaScript objects if they already have it, and always provide a way for the client to choose the name they want to install the extension as. The README.md -file on GitHub gives an example of how to install "fpmap" under a different method-name in Function.prototype.
6. LINKS
GitHuh: https://github.com/panulogic/fpmapNpm: https://www.npmjs.com/package/fpmap
_____________________________________________________________
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