4 ms·
Here's an example that I'll put in here. Let's say you have an array of strings, and you want to create a UL element with each string in an LI. You can do this
by jdmichal 13y ago
Here's an example that I'll put in here. Let's say you have an array of strings, and you want to create a UL element with each string in an LI. You can do this with a for loop just fine and everyone's happy:
var ul = document.createElement("ul");
for(var i = 0; i < strings.length; ++i) {
var text = document.createTextNode(strings[i]);
var li = document.createElement("li");
li.appendChild(text);
ul.appendChild(li);
}
However, you can pretty handily turn this into a series of functional transformations:
var ul = document.createElement("ul");
strings.map(function (string) {
return document.createTextNode(string);
}).map(function (text) {
var li = document.createElement("li");
li.appendChild(text);
return li;
}).each(function (li) {
ul.appendChild(li);
});
The next step would be decomposing the functions into their own generic entities:
function toTextNode(string) {
return document.createTextNode(string);
}
function wrap(tag, child) {
var wrapper = document.createElement(tag);
wrapper.appendChild(child);
return wrapper;
}
function append(parent, child) {
parent.appendChild(child);
}
// Helper function to provide partial-application.
// This is usually built into functional languages.
function wrapIn(tag) {
return function (child) {
return wrap(tag, child);
};
}
function appendTo(parent) {
return function (child) {
return append(parent, child);
}
}
var ul = document.createElement("ul");
strings.map(toTextNode).map(wrapIn("li")).each(appendTo(ul));
And now you have a bunch of general-purpose, easily-tested functions that you can apply in similar situations all over the place.
- mattmanser 13y agoI generally don't like double posting in a thread but... 7 lines to 30 lines, with a never-ending call stack of despair if anything ever does go wrong. That imo, is really shit code.
- BrandonM 13y agoBut the 5 helper functions are all very reusable, making the code specific to this particular usage shrink from 7 lines to 2. With a handful more usages like this, they can have a huge decrease in code size. And later on, seeing "map ... map ... each", it is immediately obvious that we are iterating over the entire array, producing new values, and then doing something with each of them. With the explicit for-loop, the unique logic is mixed in with boilerplate iteration code. Of course, by now, for (var i = 0; i < strings.length; i++) { /* something with i */ } is a common pattern for us, and our brains skip right over it. Fundamentally, though, terms like "map" and "each" are much closer to the way we approach the problem. Do you really tell yourself, "OK, I want to start with i set to 0 and increment i to match each array index, then for each of those, I'll use i to extract the ith array element, then..."? There is a lot of extra baggage going on (WTF does i have to do with creating a bullet list?), with a lot of room to make a mistake; we've just trained ourselves to overlook it. That's a huge benefit of functional programming IMO: replacing a bunch of repetitive, error-prone patterns with a higher-order function call. On a side note, it's really unfriendly to call a comprehensive, helpful example "shit code." If you can relate your opinion in a slightly more respectful way, people will be more apt to listen to you.
- theseoafs 13y ago... not to mention that off-by-one errors and other confounding bugs are really common when you loop through the elements of an array manually. That's all but impossible with map, filter, and reduce.
- jdmichal 13y agoThis is such a silly criticism, and is even sillier by the fact that I already answered it: > And now you have a bunch of general-purpose, easily-tested functions that you can apply in similar situations all over the place. EDIT: Not to mention that at least 10 of those lines could be removed with built-in currying and/or partial application, which any self-respecting functional language supports directly. JavaScript is not counted among these.
- cousin_it 13y agoHmm. In your perfect world, the code to solve that problem would look like this one-liner: strings.map(toTextNode).map(wrapIn("li")).each(appendTo(ul)); I'm not sure that's the most readable way to solve that problem. In my perfect world, the solution would be a template in a separate file: <template name="mytemplate" args="strings: array[string]" <ul> <li foreach="string in strings" content="string"> </li> </ul> </template> and in the code you'd do this: mytemplate.fill(strings); Generating HTML is one of those tasks that really deserve a DSL ;-) Also note that my solution doesn't really follow your pattern of composing several operations. The template loops over the array only once instead of three times. Maybe that's because it feels more natural to some people to nest several operations inside one loop, rather than pull out each operation as its own loop. I share that point of view as well, it feels weird to write a chunk of code differently depending on whether it's inside a loop or outside a loop. YMMV. Of course the functional approach also supports putting all operations inside one loop. You could build up the composite operation by using a lambda (which would look similar to your initial imperative example), or using some function composition operators (which I wouldn't enjoy reading afterward).
- jdmichal 13y agoUnderstood, and templating is a good solution also. I specifically wrote what I did based on the original question, which I interpreted as "what does functional programming in a UI look like?" And I wanted to show the progression from a loop to inline lambdas to reusable and generic functions.
- jhart3333 13y agoThank you for your thoughtful and informative comment. This is what I read HN for.