const not = f => a => ! f(a);
const collect = f => a => (a).map (f);
const select = f => a => (a).filter (f);
const reject = f => a => (a).filter (not(f));
const detect = f => a => (a).find (f);
const inject = f => (a,i) => (a).reduce (f,i);
let double = collect (x => x * 2);
let odd = select (x => x % 2);
let even = reject (x => x % 2);
let firstOdd = detect (x => x % 2);
let addAll = inject ((x, s) => s + x);
let numbs = [2, 4, 3, 1, 5];
let doubles = double (numbs); // -> [4, 8, 6, 2, 10]
let odds = odd (numbs); // -> [3, 1, 5]
let evens = even (numbs); // -> [2, 4]
let three = firstOdd (numbs); // -> 3
let sum = addAll (numbs, 0); // -> 15
The equivalents of Smalltalk's collect:, select:, reject:, detect: and inject: can be implemented simply with the help of ES6 arrow-function as above.
Except this is not quite the same thing, and may help to clarify the difference between Object-Oriented and Functional Programming. In Smalltalk #select: etc. are methods of collection classes which can be and are re-defined for several Collection subclasses in the standard library. You call these methods upon collection instances. What the above "functional style" code does instead is create free-standing functions select() etc. which when called with a function return another function, which then can be called with an Array instance.
Once you have the definitions for select() etc. above the code becomes about as simple as it would be with the O-O approach of having reusable predefined methods like #select: etc. in the standard library. Using Smalltalk's collection primitives you would in fact also be creating "throw-away functions" as arguments of those methods. Such arguments are instances of "BlockClosures" which is what functions are called in Smalltalk.
So what's the point of implementing the above when map() filter() reduce() and find() already exist in JavaScript? Well first I think there is value in naming all of these operations somewhat similarly, to make them easy to remember and to make it clear they really are part of the same group. Collect, select, reject, detect and inject all rhyme with each other, they are all different ways of iterating over arrays.
Secondly the above definitions do not simply duplicate the behavior of map() etc. The functions collect() etc. are not methods of Array. They are functions which take a function as argument and return another function - which can then be called with an array argument to iterate over it with the specific behavior given by the argument-function of collect() etc. This gives you a bit more reuse, you can call functions like 'even()' from many places without having to duplicate the original argument that was given to 'reject()' every time you need this behavior.
Copyright © 2017 Panu Viljamaa. All rights reserved
Wednesday, November 8, 2017
Thursday, July 27, 2017
Factory Pattern
Much has been written about the Factory-pattern, I assume. Here's my viewpoint on it.
I was puzzled a bit when I first read about it. Why? Because I was mostly programming in Smalltalk at the time. In Smalltalk the ONLY way you can create objects is by using the Factory Pattern. You create new objects by calling the class-method 'new'. So if you always follow a pattern, it's not really a pattern any more, is it? It's like fish in the water. What is this thing "water" they keep talking about?
I take it a bit further. For a Smalltalker the Factory-pattern is like air. Without it they would be dead. Without it they couldn't create any objects and thus couldn't write any Smalltalk programs and thus couldn't make a living as a Smalltalker.
But it is easy to see the benefits of using Factory (as opposed to not using it) in languages like C++ and Java and JavaScript. You create object-instances by calling (typically static) methods, not by calling constructors directly. This means you can have more than one such method per class. And you can have more than one class implementing a factory-method of the same name.
In Smalltalk in contrast there are no "constructors". You don't need them _because_ every Smalltalk-class implements the Factory pattern, because the class-method 'new' is inherited to every class, from the root-class Object.
What the Factory pattern does is it abstracts away from the IMPLEMENTATION of the results of the class- (or "static") methods used to produce the objects. Think about the proverbial Sausage Factory. You don't really need to know what goes on inside the factory. You only care that you get a Hot Dog when you order one, and you get a Bratwurst when you order that.
Depending on the class upon which you call the (same) class-method the result can be an instance of any number of different classes. You need not care which exact class it is, as long as it behaves like you expect. And depending on the (different) class-method you call on any of the classes the result can be an instance of a different classes, giving it different properties.
Above may sounds like a complicated descriptions, but it's just trying to express the two main properties, two main features of Factory-pattern:
1. A Factory can produce multiple different products, depending on what you order
2. There can be multiple factories which return their own different product even though you make the exact same "order" to each of them .
This separates the implementation = the way a factory manufactures its products, from their specification = what their products are. And it gives you flexibility in deciding whether you create one factory with many products, or many factories each producing just one or a few products.
Factory Method Pattern (Wikipedia)
Copyright © 2017 Panu Viljamaa. All rights reserved
I was puzzled a bit when I first read about it. Why? Because I was mostly programming in Smalltalk at the time. In Smalltalk the ONLY way you can create objects is by using the Factory Pattern. You create new objects by calling the class-method 'new'. So if you always follow a pattern, it's not really a pattern any more, is it? It's like fish in the water. What is this thing "water" they keep talking about?
I take it a bit further. For a Smalltalker the Factory-pattern is like air. Without it they would be dead. Without it they couldn't create any objects and thus couldn't write any Smalltalk programs and thus couldn't make a living as a Smalltalker.
But it is easy to see the benefits of using Factory (as opposed to not using it) in languages like C++ and Java and JavaScript. You create object-instances by calling (typically static) methods, not by calling constructors directly. This means you can have more than one such method per class. And you can have more than one class implementing a factory-method of the same name.
In Smalltalk in contrast there are no "constructors". You don't need them _because_ every Smalltalk-class implements the Factory pattern, because the class-method 'new' is inherited to every class, from the root-class Object.
What the Factory pattern does is it abstracts away from the IMPLEMENTATION of the results of the class- (or "static") methods used to produce the objects. Think about the proverbial Sausage Factory. You don't really need to know what goes on inside the factory. You only care that you get a Hot Dog when you order one, and you get a Bratwurst when you order that.
Depending on the class upon which you call the (same) class-method the result can be an instance of any number of different classes. You need not care which exact class it is, as long as it behaves like you expect. And depending on the (different) class-method you call on any of the classes the result can be an instance of a different classes, giving it different properties.
Above may sounds like a complicated descriptions, but it's just trying to express the two main properties, two main features of Factory-pattern:
1. A Factory can produce multiple different products, depending on what you order
2. There can be multiple factories which return their own different product even though you make the exact same "order" to each of them .
This separates the implementation = the way a factory manufactures its products, from their specification = what their products are. And it gives you flexibility in deciding whether you create one factory with many products, or many factories each producing just one or a few products.
Factory Method Pattern (Wikipedia)
Copyright © 2017 Panu Viljamaa. All rights reserved
Thursday, April 20, 2017
ES6 Structuring and Destructuring
JavaScript ES6 contains a great new feature called "Destructuring Assignment". It is great because among other things it allows you to do things like:
[a, b] = [b, a];
In other words it allows you to swap the values of two variables without the help of any extra temporary variable!
More typically and usefully Destructuring Assignment can be used to de-struct an object saving values of its chosen properties into individual variables:
var {a, b} = {a: 1, b: 2}
// a -> 1, b -> 2
Another great ES6 feature is called "Shorthand property name". That allows you to create objects out of variables so that the name of the variable becomes the property-key and value of the variable becomes the value (of the property). Like this:
var ding = 55;
var myOb = {ding};
ok (myOb.ding === 55);
What may not be immediately obvious is that Destructuring Assignment and Shorthand Property Name are two sides of the same coin. In fact I think Shorthand Property Name could and should be called simply "Structuring".
Here's an example of how structuring followed by destructuring gives us back the original (like flipping a coin):
var ding = {};
var originalDing = ding;
var myOb = {ding}; // STRUCTURING
ding = "forget about it";
ok (ding !== originalDing);
var {ding} = myOb ; // DE-STRUCTURING
ok (ding === originalDing);
(Note, "ok()" is my simple assertion utility-function)
LINKS:
Copyright © 2017 Panu Viljamaa. All rights reserved
[a, b] = [b, a];
In other words it allows you to swap the values of two variables without the help of any extra temporary variable!
More typically and usefully Destructuring Assignment can be used to de-struct an object saving values of its chosen properties into individual variables:
var {a, b} = {a: 1, b: 2}
// a -> 1, b -> 2
Another great ES6 feature is called "Shorthand property name". That allows you to create objects out of variables so that the name of the variable becomes the property-key and value of the variable becomes the value (of the property). Like this:
var ding = 55;
var myOb = {ding};
ok (myOb.ding === 55);
What may not be immediately obvious is that Destructuring Assignment and Shorthand Property Name are two sides of the same coin. In fact I think Shorthand Property Name could and should be called simply "Structuring".
Here's an example of how structuring followed by destructuring gives us back the original (like flipping a coin):
var ding = {};
var originalDing = ding;
var myOb = {ding}; // STRUCTURING
ding = "forget about it";
ok (ding !== originalDing);
var {ding} = myOb ; // DE-STRUCTURING
ok (ding === originalDing);
(Note, "ok()" is my simple assertion utility-function)
LINKS:
Copyright © 2017 Panu Viljamaa. All rights reserved
Thursday, April 13, 2017
Why Prototypical Languages (like ES6) need Classes
In "prototypical languages" like JavaScript you can use object-instances to do much anything. Typically you do this by defining "constructors" to specify the DEFAULT properties of the group of objects created by that constructor.
After the constructor gives you the new instance you can re-assign its properties including methods any way you like. Then, and this is the big draw of Prototypical Programming, you can use that modified object as the prototype for further objects. Any object can serve as the prototype for any number of further objects ... which can serve as the prototype of yet further objects and so on. Convenient.
So why does ECMAScript 2015 need "classes"?
In the prototypical paradigm any object can be a "version" of any other object. This may sound like a great idea at first, a great extension, instances can act as "classes" too. Why not! More power to you. Why artificially limit the ability to use any object as a "prototype" from which new objects similar to it are derived?
The short answer is: Classes are constraints. Classes are contracts . Classes are invariants. Those make your program's behavior easier to reason about.
Classes describe the properties of all their instances, so when you see an instance, and you know its class, you know what properties, especially, what methods it has. If you know the class you also know what those methods do, or are supposed to do.
Now if any object can have its own set of methods, which is possible in the prototypical paradigm, it becomes harder to know what properties and methods any instance has, what are its methods and what can you expect from their behavior. That makes it harder to UNDERSTAND such a program, whether written by you or someone else.
Why understandability is the most important property of Software?
Understanding what software does, is THE great big challenge of Software Engineering. If you don't understand how a program works you can't modify it because any small change in one place could have big effects in other parts of the program, possibly crashing it under some conditions. If you can't modify it you can't improve it. Therefore, if you have to modify it you do it very carefully. Which means you can only improve it very slowly. And each improvement is getting more expensive (assuming each improvement is as difficult to understand as the previous ones).
If you can understand your program, you can improve any other property of it. If you can't, you can't.
A class is a declaration of the properties and behavior of the set of objects created by that class. It means you know much about any object simply by knowing its class. That makes it much easier to understand what your program does, as a whole.
If it walks like a Duck it may be a Duck. But it may bark like a Dog. It might. You just don't know.
If it IS a Duck, it will not bark like a Dog. If it is an instance of a class, it will behave as instances of that class do in all the ways prescribed by that class. That's why we need classes.
LINKS:
Copyright © 2017 Panu Viljamaa. All rights reserved
After the constructor gives you the new instance you can re-assign its properties including methods any way you like. Then, and this is the big draw of Prototypical Programming, you can use that modified object as the prototype for further objects. Any object can serve as the prototype for any number of further objects ... which can serve as the prototype of yet further objects and so on. Convenient.
So why does ECMAScript 2015 need "classes"?
In the prototypical paradigm any object can be a "version" of any other object. This may sound like a great idea at first, a great extension, instances can act as "classes" too. Why not! More power to you. Why artificially limit the ability to use any object as a "prototype" from which new objects similar to it are derived?
The short answer is: Classes are constraints. Classes are contracts . Classes are invariants. Those make your program's behavior easier to reason about.
Classes describe the properties of all their instances, so when you see an instance, and you know its class, you know what properties, especially, what methods it has. If you know the class you also know what those methods do, or are supposed to do.
Now if any object can have its own set of methods, which is possible in the prototypical paradigm, it becomes harder to know what properties and methods any instance has, what are its methods and what can you expect from their behavior. That makes it harder to UNDERSTAND such a program, whether written by you or someone else.
Why understandability is the most important property of Software?
Understanding what software does, is THE great big challenge of Software Engineering. If you don't understand how a program works you can't modify it because any small change in one place could have big effects in other parts of the program, possibly crashing it under some conditions. If you can't modify it you can't improve it. Therefore, if you have to modify it you do it very carefully. Which means you can only improve it very slowly. And each improvement is getting more expensive (assuming each improvement is as difficult to understand as the previous ones).
If you can understand your program, you can improve any other property of it. If you can't, you can't.
A class is a declaration of the properties and behavior of the set of objects created by that class. It means you know much about any object simply by knowing its class. That makes it much easier to understand what your program does, as a whole.
If it walks like a Duck it may be a Duck. But it may bark like a Dog. It might. You just don't know.
If it IS a Duck, it will not bark like a Dog. If it is an instance of a class, it will behave as instances of that class do in all the ways prescribed by that class. That's why we need classes.
LINKS:
Copyright © 2017 Panu Viljamaa. All rights reserved
Tuesday, April 4, 2017
Technical Debt Increases Agility
You might think that "Agility" is a good thing, and "Technical Debt" is bad. Right?
Well it depends.
Of course it's great to have no debt. But if you had taken that loan last year you could have invested in the stock-market and become rich and it would be even greater than just "having no debt".
When you're working on a programming project you might spend most of your time refactoring your code, to avoid technical debt. Or you could spend your time getting the software out to the users, no matter how much technical debt it carries.
Making sure your software has "no technical debt" will help you in the FUTURE when you need to change and adapt and maintain your software. But using resources now to get rid of technical debt decreases your CURRENT AGILITY, your ability to produce value to the users right now. If you can't help users fast you're not very agile.
There's no clear-cur rule as to how much debt a business should carry, or how much technical debt a given software project should have. Debt is not a bad thing if it helps you be agile, right now.
https://twitter.com/panulogic/status/849331316218896385
Copyright © 2017 Panu Viljamaa. All rights reserved
Well it depends.
Of course it's great to have no debt. But if you had taken that loan last year you could have invested in the stock-market and become rich and it would be even greater than just "having no debt".
When you're working on a programming project you might spend most of your time refactoring your code, to avoid technical debt. Or you could spend your time getting the software out to the users, no matter how much technical debt it carries.
Making sure your software has "no technical debt" will help you in the FUTURE when you need to change and adapt and maintain your software. But using resources now to get rid of technical debt decreases your CURRENT AGILITY, your ability to produce value to the users right now. If you can't help users fast you're not very agile.
There's no clear-cur rule as to how much debt a business should carry, or how much technical debt a given software project should have. Debt is not a bad thing if it helps you be agile, right now.
https://twitter.com/panulogic/status/849331316218896385
Copyright © 2017 Panu Viljamaa. All rights reserved
Saturday, March 25, 2017
Misssing Pattern in Model-View-Controller
MVC confusion must largely stem from the fact that the original MVC pattern was implemented in Smalltalk and in Smalltalk everything is an Object. With the Web this is not so. You have HTML, you have web-server, you have browser and you have database. It gets confusing trying to fit it all into "MVC". Understanding Model-View-Controller is an example of a great explanation of Model-View-Controller for the Web. Still I think it misses one crucial part of MVC as originally envisioned, and implemented in Smalltalk.
What is the main point of Smalltalk MVC seldom included in descriptions of "Web-MVC"? It is not that Model View and Controller are "separate". It is pretty obvious they should be. No spaghetti-code please. The main point is what makes M V and C capable of working together EVEN THOUGH they are "separate"...
The original MVC solution for connecting the parts is the Observer-pattern. A View subscribes to events broadcast by the model which are of relevance to that particular View. This means the model is unaware of the existence of the view, yet it is able to notify the view(s) when specific aspects of it change. Yes "aspect" is a key term in early descriptions of MVC.
The Model "broadcasts" events about changes happening to it. But implementation-wise the "system" must take care those events are propagated to the proper recipients. This invisible part of MVC, its event-delivery sub-system goes largely missing in most explanations of "Web-MVC".
So, MVC is more than M + V + C. It is M + V + C + event-delivery sub-system that allows M V and C to communicate with each other by event subscription, thus remaining minimally dependent on each other.
Another misconception must be that there is a Model, a View and a Controller. In the original Smalltalk MVC a major emphasis was on the idea that there were MULTIPLE VIEWS for each model, each displaying a different "aspect" of it, and having its own controller. Models are often composed of parts which tmelselves are models too. It is really more like Models-Views-Controllers. In Web-MVC the web-page is typically the one and only "view" presenting some underlying something, vaguely referred to as "model".
There is value in categorizing parts of any application into a Model, View and Controller. But that is not close to being the same as the MVC-pattern in Smalltalk.
A pattern is in the eye of the beholder. The pattern I see in Smalltalk called MVC is multiple Views presenting different aspects of the model, communicating with it via event-subscription, driven by user-interaction events produced by multiple Controllers, of different classes. Controller modifies the model, model broadcasts information about changes to its different aspects, views that are "tuned in" to the specific channel for that aspect receive notifications of changes to it, so they can update their state, their "looks" by asking the view for the current value or state of the "aspect" in question.
The essence of MVC is not that there is M, V and C. It is how the M and V and C communicate, with each other.
Links:
Understanding model-view-controller
Model-View-Controller
Observer-pattern
Copyright © 2017 Panu Viljamaa. All rights reserved
https://twitter.com/panulogic
What is the main point of Smalltalk MVC seldom included in descriptions of "Web-MVC"? It is not that Model View and Controller are "separate". It is pretty obvious they should be. No spaghetti-code please. The main point is what makes M V and C capable of working together EVEN THOUGH they are "separate"...
The original MVC solution for connecting the parts is the Observer-pattern. A View subscribes to events broadcast by the model which are of relevance to that particular View. This means the model is unaware of the existence of the view, yet it is able to notify the view(s) when specific aspects of it change. Yes "aspect" is a key term in early descriptions of MVC.
The Model "broadcasts" events about changes happening to it. But implementation-wise the "system" must take care those events are propagated to the proper recipients. This invisible part of MVC, its event-delivery sub-system goes largely missing in most explanations of "Web-MVC".
So, MVC is more than M + V + C. It is M + V + C + event-delivery sub-system that allows M V and C to communicate with each other by event subscription, thus remaining minimally dependent on each other.
Another misconception must be that there is a Model, a View and a Controller. In the original Smalltalk MVC a major emphasis was on the idea that there were MULTIPLE VIEWS for each model, each displaying a different "aspect" of it, and having its own controller. Models are often composed of parts which tmelselves are models too. It is really more like Models-Views-Controllers. In Web-MVC the web-page is typically the one and only "view" presenting some underlying something, vaguely referred to as "model".
There is value in categorizing parts of any application into a Model, View and Controller. But that is not close to being the same as the MVC-pattern in Smalltalk.
A pattern is in the eye of the beholder. The pattern I see in Smalltalk called MVC is multiple Views presenting different aspects of the model, communicating with it via event-subscription, driven by user-interaction events produced by multiple Controllers, of different classes. Controller modifies the model, model broadcasts information about changes to its different aspects, views that are "tuned in" to the specific channel for that aspect receive notifications of changes to it, so they can update their state, their "looks" by asking the view for the current value or state of the "aspect" in question.
The essence of MVC is not that there is M, V and C. It is how the M and V and C communicate, with each other.
Links:
Understanding model-view-controller
Model-View-Controller
Observer-pattern
Copyright © 2017 Panu Viljamaa. All rights reserved
https://twitter.com/panulogic
Thursday, February 2, 2017
How to explain Threads
People new to programming might have hard time understanding what are "threads". How are they different from "programs"? There's a simple metaphor you can use to explain the concept of "threads":
Your program is like a labyrinth. You can't walk "through it", you must follow the paths that exist. There are many branches in those paths you can take. You can go forwards and you can reverse your direction to return to an earlier point. You can pick things up to carry with you while inside the labyrinth, maybe just some information, maybe take photographs.
The program has run its course when you return back to the entry-point and exit. You still have the things you picked along the route, the photographs you took etc. Those are your output. What you took with you into the labyrinth was your input, say a map which guided your choice of turns at the branch-points.
The above is single-threading. You moving inside the labyrinth is like the program-counter in your program's execution environment pointing to different locations in the program being "run". (Yes you can run in a labyrinth too, you must if a Minotaur is chasing).
Now leash multiple visitors into the same labyrinth, more or less at the same time, concurrently . They will choose their own paths according to their own maps. They might communicate with each other by leaving notes on the walls for others to read. Some visitors might overwrite notes written by others, probably not a very good practice but certainly possible. This is multi-threading: Multiple visitors in the labyrinth at the same time. Each visitor walking inside the labyrinth is one thread.
Why are they called "threads"? I can think of one reason. In the Minotaur-myth Theseus entered the labyrinth with a ball of thread. He tied the start of the thread to the entry point of the labyrinth. Then he unwound the thread as he moved deep into the Labyrinth. After he found the Minotaur and killed it he needed a way to find his way back out. He simply followed the unwound thread.
https://twitter.com/panulogic
Your program is like a labyrinth. You can't walk "through it", you must follow the paths that exist. There are many branches in those paths you can take. You can go forwards and you can reverse your direction to return to an earlier point. You can pick things up to carry with you while inside the labyrinth, maybe just some information, maybe take photographs.
The program has run its course when you return back to the entry-point and exit. You still have the things you picked along the route, the photographs you took etc. Those are your output. What you took with you into the labyrinth was your input, say a map which guided your choice of turns at the branch-points.
The above is single-threading. You moving inside the labyrinth is like the program-counter in your program's execution environment pointing to different locations in the program being "run". (Yes you can run in a labyrinth too, you must if a Minotaur is chasing).
Now leash multiple visitors into the same labyrinth, more or less at the same time, concurrently . They will choose their own paths according to their own maps. They might communicate with each other by leaving notes on the walls for others to read. Some visitors might overwrite notes written by others, probably not a very good practice but certainly possible. This is multi-threading: Multiple visitors in the labyrinth at the same time. Each visitor walking inside the labyrinth is one thread.
Why are they called "threads"? I can think of one reason. In the Minotaur-myth Theseus entered the labyrinth with a ball of thread. He tied the start of the thread to the entry point of the labyrinth. Then he unwound the thread as he moved deep into the Labyrinth. After he found the Minotaur and killed it he needed a way to find his way back out. He simply followed the unwound thread.
__________________________________
Copyright © 2017 Panu Viljamaa. All rights reserved https://twitter.com/panulogic
Monday, January 30, 2017
Function.prototype.map IV
The previous episodes Function.prototype.map, Function.prototype.map II, Function.prototype.map III describe how to use Function.prototype.map with three argument-types: Array, Object and Function. These extensions to Function.prototype provide support for and take advantage of Functional Programming (a.k.a "FP") in JavaScript.
It is kind of fitting that additional support for Functional Programming in JavaScript should be provided as additional methods of the JavaScript class Function. Don't you think?
This blog-post describes some additional features not previously covered and the fpmap() -library as a whole. The described features are now implemented in the "fpmp" download available from npm and GitHub, see links below.
var arr = [1,2,3];
var a2 = arr.map (double); // -> [2, 4, 6]
Above "double" is a function which returns its argument times two. Writing such code I occurred to me to ask did I write it correctly. Or should I have instead written:
var a3 = double.map (arr); // ???
Luckily I had it correct no problem. I usually don't' make errors like that I congratulated myself. But then I started thinking, is there any rule-of-thumb I could use to easily remember the correct order? Is there perhaps something obviously wrong in the "wrong order" ?
I couldn't find anything obviously wrong with it. So I started thinking, why does it have to matter which way I put it? If both ways would produce the same result then I could more easily remember the simplest rule: Order doesn't matter! If something seems good either way, we shouldn't have to think much about which way to do it.
So, went to work and implemented the function "fpmap()". Calling it to first install Function.prototpe.map(), it now allows me to write:
var a3 = double.map (arr); // -> [2,4,6]
With [1,2,3].map(double) the "recipient" must always be an Array, and it seems argument-type must be Function, to make it useful in general. Regardless of what type the Array elements are, you can then always put in as argument some function that accepts those array-elements as argument.
With double.map([1,2,3]) it came obvious to ask a follow-up question: What if I use something else as argument, perhaps an Object like: "double.map({x: 1, y: 2})" ? Is there something useful such an expression could do? YES OF COURSE. It can iterate over the fields of the argument-object, like {x:1, y:2}.
You could't really do such an expansion with Array.prototype.map(), unless you start adding the method "map()" to all built-in prototypes in JavaScript. That is a possibility but there's no need for that if we can make Function.prototype.map treat different types of arguments in different ways. The area of the standard library that needs to be extended this way stays smaller, and you choose and alternative method-name to use as desired. You can install it as Function . prototype . map9() if you wish to use a more unique name
With this re-arrangement there seems to be no reason why the argument could not be a non-Array "object". Which leads to the further question: What else? What are the argument-types that could be passed to Function .prototype .map, to accomplish something useful, some economies of the amount of code you must write, read, and understand? What should be the behavior of 'map' with such possible argument-types?
But turns out there are still a few more argument-types with different, useful behavior. In the end, currently "fpmap" now supports the following behaviors on the following six (6) argument-types:
For detailed documentation of the above and other features of fpmap() see the unit-tests-file fpmap_test.js. Tests don't lie. The code of the tests serve as examples of how to use fpmap() with different arguments, and what to expects as result. There's also explanatory comments. The README.md of course is a good source for documentation as well.
Could there still be other additional, useful argument-types besides the above? Possibly. But the above is a good start.
Npm: https://www.npmjs.com/package/fpmap
Twitter: https://twitter.com/panulogic
Reuse of source-code in this blog-post is allowed under the terms of
It is kind of fitting that additional support for Functional Programming in JavaScript should be provided as additional methods of the JavaScript class Function. Don't you think?
This blog-post describes some additional features not previously covered and the fpmap() -library as a whole. The described features are now implemented in the "fpmp" download available from npm and GitHub, see links below.
1. WHERE DID IT ALL COME FROM?
JavaScript standard Array.prototype.map() can be used to execute a given function once once for each of the array-elements and get back an array containing the results of each such call:var arr = [1,2,3];
var a2 = arr.map (double); // -> [2, 4, 6]
Above "double" is a function which returns its argument times two. Writing such code I occurred to me to ask did I write it correctly. Or should I have instead written:
var a3 = double.map (arr); // ???
Luckily I had it correct no problem. I usually don't' make errors like that I congratulated myself. But then I started thinking, is there any rule-of-thumb I could use to easily remember the correct order? Is there perhaps something obviously wrong in the "wrong order" ?
I couldn't find anything obviously wrong with it. So I started thinking, why does it have to matter which way I put it? If both ways would produce the same result then I could more easily remember the simplest rule: Order doesn't matter! If something seems good either way, we shouldn't have to think much about which way to do it.
So, went to work and implemented the function "fpmap()". Calling it to first install Function.prototpe.map(), it now allows me to write:
var a3 = double.map (arr); // -> [2,4,6]
2. AN INSIGHT AND A REVELATION
This made me feel almost like I had discovered complex numbers! Why? Because it seemed the possible ways of using the same operation, with different types of arguments had now greatly expanded. It was almost like jumping out of line to the plane around it! Let me explain...With [1,2,3].map(double) the "recipient" must always be an Array, and it seems argument-type must be Function, to make it useful in general. Regardless of what type the Array elements are, you can then always put in as argument some function that accepts those array-elements as argument.
With double.map([1,2,3]) it came obvious to ask a follow-up question: What if I use something else as argument, perhaps an Object like: "double.map({x: 1, y: 2})" ? Is there something useful such an expression could do? YES OF COURSE. It can iterate over the fields of the argument-object, like {x:1, y:2}.
You could't really do such an expansion with Array.prototype.map(), unless you start adding the method "map()" to all built-in prototypes in JavaScript. That is a possibility but there's no need for that if we can make Function.prototype.map treat different types of arguments in different ways. The area of the standard library that needs to be extended this way stays smaller, and you choose and alternative method-name to use as desired. You can install it as Function . prototype . map9() if you wish to use a more unique name
With this re-arrangement there seems to be no reason why the argument could not be a non-Array "object". Which leads to the further question: What else? What are the argument-types that could be passed to Function .prototype .map, to accomplish something useful, some economies of the amount of code you must write, read, and understand? What should be the behavior of 'map' with such possible argument-types?
3. THE USEFUL ARGUMENT-TYPES OF Function.prototype.map
The previous blog-posts described three argument-types so far: Array, (non-Array) Object, Function. Yes. Function.prototype.map() can also take a function as its argument, the result being the "composition" of the two functions. And you can compose a whole series, a "pipeline" if you will: funkA . map(funkB) . map(funkC) ;But turns out there are still a few more argument-types with different, useful behavior. In the end, currently "fpmap" now supports the following behaviors on the following six (6) argument-types:
A) Array
Like standard Array.prototype.map() but with recipient and the argument-types switched.B) Object
Similar to Arrays but iterates over named properties of the object rather than array indexes.C) Function
Implements function-composition. Like: var result = funkA . map(funkB) . map(funkC) ;D) RegExp
Returns a function which can be used to Iterate over all matches of a given Regular Expression for any string-argument.E) Number
Returns a function which when given an initial argument will call the source-function with it, then call it again with the results and so on N times, where N is the number that was given as argument to fmap(N). Naturally requires that the source-function result-type is the same as its argument type. Useful for creating numeric series like Fibonacci's but also for building arbitrary recursive data-structures.F) String
Iterates by repeatedly calling a function that incrementally consumes parts of the argument string, in effect parsing it according to the language specified by the recipient-function.4. WHAT UNITES THEM ALL
What unites the different argument types described above is they are all used in the same manner, as argument to Function.prototype.map. So it's kind of easy to remember how to use them, if not exactly what each of them exactly does. But that you can look up from documentation.For detailed documentation of the above and other features of fpmap() see the unit-tests-file fpmap_test.js. Tests don't lie. The code of the tests serve as examples of how to use fpmap() with different arguments, and what to expects as result. There's also explanatory comments. The README.md of course is a good source for documentation as well.
Could there still be other additional, useful argument-types besides the above? Possibly. But the above is a good start.
5. LINKS
GitHuh: https://github.com/panulogic/fpmapNpm: https://www.npmjs.com/package/fpmap
Twitter: https://twitter.com/panulogic
_____________________________________________________________
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
Thursday, January 19, 2017
Function.prototype.map III
This is starting to sound like the band Chicago. First they came up with the album "Chicago", then "Chicago II", then "Chicago III", and so on. I want to assure you this is the last blog-post in my series of "Function.prototype.map" -blog-posts. But this needs to be written ...
If you want to catch up with the previous episodes, they are Function.prototype.map and Function.prototype.map II.
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:
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.
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";
}
}
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.
Npm: https://www.npmjs.com/package/fpmap
Reuse of source-code in this blog-post is allowed under the terms of
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
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:
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
Thursday, January 5, 2017
Function.prototype.map
1. PROPOSAL
This blog-post unofficially proposes a new standard API-function for JavaScript: Function.prototype.map. While it may not become part of any official standard ever, you can use it to make your code simpler in some cases, by copying the code for it from below.The specification for Function.prototype.map is simple because it is stated in terms of the existing Array.prototype.map:
- IF for any array arrayX and function functionX the expression arrayX.map ( functionX, arg2 ) produces result arrayY, THEN functionX.map ( arrayX, arg2 ) must produce the same result except without any undefined elements.
2. EXAMPLE
[1,2,3].map(doubleF); // -> [2,4,6], standard
doubleF.map([1,2,3]); // -> [2,4,6], proposedifOddF .map([1,2,3]); // -> [1,3] , proposed
function doubleF (currentValue, index, array)
{ return currentValue * 2;
}
function ifOddF (currentValue, index, array)
{ if (currentValue % 2)
{ return currentValue;
}
}
3. IMPLEMENTATION
Below shows a simple implementation of Function.prototype.map. The first paragraph of code does the equivalent of what Array.prototype.map would do, if given the "recipient-function" as its argument. The 2nd code-paragraph removes undefined elements before returning the result without them:
Function.prototype.map = function ()
{ var args = [].slice.call (arguments);
var args2 = [this].concat (args.slice(1));
var result1 = [].map.apply (args[0], args2);
var result2 = [];
for (var j=0; j < result1.length; j++)
{ var e = result1[j];
if (e !== undefined)
{ result2.push(e);
}
}
return result2;
4. MOTIVATING EXAMPLE
Below code-example shows the function filesOfCurrentDir()which returns an array containing the absolute path-names of all files in the current directory in Node.js. We want to know what are the files apart from sub-directories. Node.js does not have a simple API for doing this, so we need to create our own.
Node.js does have the function Fs.readdirSync() for listing the elements of a directory-path given as argument. That returns an array of the names of the directory components including files and the sub-directories. But we want more than the names, we want path-names which uniquely identify their corresponding files.
The solution is to create another function ifFile() and call the standard function bind() with it as argument to return a derived function which will always call the original function with the 2nd argument of bind() as the first argument of ifFile(). That sounds complicated. Therefore, it's worth the while to try to make everything else as simple as possible. We can use Function.prototype.map() to help do that.
The code for filesOfCurrentDir() below shows two ways of trying to get the files in the directory, the first of which doesn't quite work:
var filesA = fileAndDirNames // A
.map (ifFile.bind (null, currentDir));
.map (ifFile.bind (null, currentDir));
Above uses the standard Array.prototype.map() to call a function derived with bind() from ifFile() for each file- and directory -name in the current directory. We want the result to be an Array of the absolute path-names of files in the directory.
But there's a problem: ifFile() is meant to filter out directory names by returning undefined if a path-name points to a directory. Therefore filesA now contains the absolute path-names of every file in the directory PLUS an undefined value for every sub-directory of it.
But there's a problem: ifFile() is meant to filter out directory names by returning undefined if a path-name points to a directory. Therefore filesA now contains the absolute path-names of every file in the directory PLUS an undefined value for every sub-directory of it.
We could easily write a loop for removing the undefined values. However we get that done automatically if we use Function.prototype.map() instead, like this:
var filesB = ifFile.bind (null, currentDir) // B
.map (fileAndDirNames);
A more fundamental reason I prefer Function.prototype.map() over its Array-cousin in this case, is seen by comparing the code-structure of the two alternatives. The first one contains nested parenthesis, the second doesn't!
Trying to understand an expression that contains nested parenthesis is more difficult and laborious and easier to misunderstand. When reading the first example (A) from left to right you can't understand what the whole expression does without first understanding what the argument expression does, then you have to "back up" to restart calculating the value of the containing expression.
Whereas reading the 2nd expression (B) from left to right you can parse and understand what each step independently does because there are no "inner expressions". Just know that the data flows from left to right, like in a pipeline.
You could rewrite the first example to get rid of nested parenthesis by taking the inner expression out and storing it into a temporary variable which you then use as argument. But then you would have to make a similar "jump back" to recall what was the value you stored into the variable. And more variables means more locations that can hold a wrong value. There's a cost to every variable.
function filesOfCurrentDir (){
var Path = require('path');
var Fs = require('fs') ;
var currentDir = __dirname ;
var fileAndDirNames = Fs.readdirSync (currentDir);
var filesA = fileAndDirNames
.map (ifFile.bind (null, currentDir));
// Above ALMOST does it, but the array 'filesA'
// contains undefined for each sub-directory.
// Using Function.prototype.map() we get rid of
// those automatically. NOTE: Above call should
// be removed after studying it and understanding
// why it does not work.
var filesB = ifFile.bind (null, currentDir)
.map (fileAndDirNames);
return filesB ;
function ifFile (currentDir, fileOrDirName)
{
var path = Path.join(currentDir, fileOrDirName);
if (Fs.lstatSync(path).isFile())
{ return path;
}
// else: return nothing, a.k.a undefined
}
}
4. DISCUSSION
The JavaScript API of Function would seem to be an obvious candidate for extension, for improved support for Functional Programming in JavaScript.The downside of per-programmer extensions which add something to the basic JavaScript classes of course is that if everybody comes up with their own extension, there will be conflicts between the implementations. But the extension proposed above is simple and benefits of it substantive enough that I've found no reason not to use it.
Having Function.prototype.map available in addition to Array.prototype.map helps us get rid of nested argument-expressions without having to add extra temporary variables which would require more lines of code.
Stripping away undefined values allows us to use Function.prototype.map for not only transforming arrays but also for filtering out elements from the results of the transformation.
Note what the function Function.prototype.map presented here does is in no way dependent on what method-name of Function.prototype it is installed as. You can decide which method-name to use for it on a module-by-module basis. What an extension-function does is dependent on which prototype it is is installed in.
5. HOW TO USE IT
Copy the code-segment from Section 3. to the start of your JavaScript-file. After that you can call 'map()' on any function with any array as argument.
1. Array.prototype.map: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
2. Function.prototype.bind: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
3. fs.readdirsync: https://nodejs.org/api/fs.html#fs_fs_readdirsync_path_options
4. fs.lstatsync: https://nodejs.org/api/fs.html#fs_fs_lstatsync_path
6. UPDATE !
A newer blog-post Function.prototype.map II provides and describes an improved implementation for Function.prototype.map, with the ability to loop over non-arrays as well7. LINKS
If you 're not familiar with some of the external functions used in the code-example you can find their documentations here:1. Array.prototype.map: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
2. Function.prototype.bind: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
3. fs.readdirsync: https://nodejs.org/api/fs.html#fs_fs_readdirsync_path_options
4. fs.lstatsync: https://nodejs.org/api/fs.html#fs_fs_lstatsync_path
_____________________________________________________________
Copyright © 2017 Panu Viljamaa. All rights reserved unless otherwise noted.Reuse of code examples in this blog-post is granted under the terms of
Creative Commons Attribution 4.0 International (CC BY 4.0) -license
Subscribe to:
Posts (Atom)