Hacker News new | ask | show | jobs
by hcarvalhoalves 4819 days ago
Ideally, we would all write programs by assembling declarations, imperative code would be limited to internal implementations. That's largely the reason it's good practice to abstract away implementation behind APIs - what you have left is almost a pure declarative language, or DSL, that maps 1:1 your problem domain, without looping or branching or I/O (which are computation details).

Taking the example from the original article, it would be more akin to:

    // Implementation
    function double(n) {
      return n * 2;
    }

    // Declaration
    [1,2,3,4,5].map(double)
    => [2,4,6,8,10]
1 comments

And, see, I would actually invert further. The declaration should be:

    elements = [1,2,3,4];
    doubledElements = doubleElements(elements)
Basically, if you see the words map, fold, reduce in your code, you are probably not as easy to understand as you'd like to think.

Of course, in the cooking methaphor, I'm ok with mutating elements and just doing:

    elements = [1,2,3,4];
    doubleElements(elements);
This clearly has issues if multiple "cooks" are working with elements. But is ridiculously easy to intuit regardless. (Precisely because in real life so many things are changed by imperative commands.)
> And, see, I would actually invert further. The declaration should be:

But then you lose pureness, right? The whole point of using high-order functions is allowing you to be as declarative as mathematics, so you can just operate functions together.

Consider that in the first example, I only need to write the implementation for doubling a number n, while the `doubleElements` implementation is too specific, would throw the other half of the code back into imperative land.

Only in my example that relied on mutations. The first can all be implemented with pure functions just fine. Indeed, I was assuming it would be, hence the assignment to a new variable.

I suppose I should have said that the doubleElements implementation would likely be that map one liner. (Though, it needn't be. One could exploit custom knowledge of the domain there to do crazy crap like memoize the calls.)

That make sense?

> Only in my example that relied on mutations. The first can all be implemented with pure functions just fine. Indeed, I was assuming it would be, hence the assignment to a new variable.

Yes, the first example uses pure functions, but I guess you're confusing pure functions with HOFs [1]. The point is only having to write the implementation to double one number, and extrapolating it by composition. Consider that in your example, for instance, you would need a `doubleHash` function for hashes, and so on.

http://en.wikipedia.org/wiki/Higher-order_function

I did not realize we were debating HOF versus pure functions.

That is, I'm fine with using both the pure and HO functions. I just think hiding the HOF ones behind a normal function call is usually a big win for readability.

So, to do the full example:

    elements = [1,2,3,4]
    doubledElements = doubleElements(elements)
    function doubleElements(e) {
        function double(n) { return n*2; }
        return e.map(double);
    }
Where I would assume the "reader" code would only have the first two lines. The rest would be behind the implementation layer. If the double function would be used elsewhere, no need to scope it to doubleElements.