Hacker News new | ask | show | jobs
by pvidler 5065 days ago
Do maps, folds and filters become as natural as for-loops in time? (If you have already been exposed to more traditional techniques). They strike me as similar to regular expressions, in the sense that however long I spend using them I will always have to think it through carefully when I encounter a new use.

By contrast, a for-loop is spelled out for me. They are certainly longer, but I guess they match my thought process more closely. I suppose the question is, do I think this way because of my prior exposure to for-loops, or would it be the same if I was exposed to functional programming first? I'm really not sure.

4 comments

Yes, map, reduce and filter feel much more natural to me than explicit loops for their respective use cases. Let's try writing an example in English:

    Give me the items from the collection foo for which the function bar returns true.

    Create an empty collection baz and a counter i with a value of 0. Until i reaches the length of the collection foo, do the following, then increment i and repeat: call the function bar with the element of foo at position i. If bar returns true, append the element to baz. After the loop is complete, return baz.
The for loop is pretty awkward just to express in English, while the filter is straightforward enough that most non-programmers would have a pretty solid understanding of what they'll get.
Fascinating, thanks. I would have said that the for-loop is at least somewhat intuitive -- the step-by-step (lather, rinse, repeat) nature resembles the way most instruction sets are written. Not to mention flow charts, which most people have been exposed to at some stage.

Do you have any advice for languages and techniques for getting the necessary exposure to these concepts? I'm not in a position to use FP through work, so I'd need some easy intro and way to practice.

Do you have any advice for languages and techniques for getting the necessary exposure to these concepts?

Functional programming tends to emphasize data flow and leave control flow as an (often hidden) implementation detail. So, I find it’s helpful to think of algorithms in terms of transformations of data rather than series of instructions.

For example, what values do I start with, and are they in (unordered) sets, (ordered) sequences, or something more intricate? Then, what do I want to end up with?

Now, how can I get from one to the other via a series of transformations or combinations of the data? Am I turning one set/sequence/whatever into another, operating on the individual elements? If so, that’s probably some sort of map operation. Am I combining element of a sequence somehow? If I’ve already got a sequence, that’s probably a reduce or something like it. But what if I’ve only got an unordered set? Then maybe I need to impose some ordering first. Can that order be arbitrary, or is there really some implicit order that gets me to working with a well-defined sequence? Maybe I need to combine multiple sequences somehow. If each element in one sequence corresponds to a single element in the other, that’s probably some sort of zip operation. If it’s a many-to-many matching, that might be some sort of nested recursive algorithm that walks each sequence one inside the other, or maybe I can generate an outer product so I’ve got a one-dimensional list of all possible pairs and then do something simpler with that.

After a while, you get to recognise basic underlying data structures like sets, sequences, and trees, and you get to recognise which common operations make sense for each type. You can map just about anything, but to reduce you need a sequence. You can make a sequence from a set by imposing an arbitrary order if necessary. You can make a sequence from a tree in several ways, for example by traversing it in depth-first or breadth-first. And so on.

Sometimes you’ll need to do something that isn’t a convenient, ready-made transformation like a filter or zip, and at that point you start thinking about using recursion to traverse the relevant data structure(s) “manually”. Once you’ve done that a few times, you might start to see new kinds of recurring logic within your code, which you can abstract away into their own higher order functions to join the standard toolbox next to fold and friends.

You can get into all kinds of fun and games with representing effects and controlling the interactions between them as well. However, I’d suggest that if you can start to think of simple problems in terms of things like transforming and combining structured data and then move on to implementing more advanced algorithms by combining the simpler building blocks, that’s probably the best way to get your feet wet without getting out your depth in the early days.

Ruby, especially the standard Array library. Look at stuff like select, each, and whatnot, methods that take a block. Try and write (or rewrite) a toy program you have lying around and make sure you try to use those.

jQuery is another option if you want web-related stuff, esp. http://api.jquery.com/category/traversing/.

Don't thank us for exposing this to you just yet, though. :) You can use this pattern in some mainstream programming languages, but not all of them--- typically it's not idiomatic in Java or C++.

I think there's still a gap between how you're thinking about it and how I am.

Yes, a for loop translates pretty cleanly to a flowchart. It's not hard to understand how it works. The problem is that I'm usually thinking about the result I want in the case of sequence transformations like map and filter, not the process of generating it.

They do and I started with Qbasic. Actually I find them more natural and find for-loops to be cumbersome. Fold took the longest.

I think how well things stick is a function of how much you think the effort of learning it is worth and how much time you spend on it. Regular Expressions never stuck for me either, I keep having to relearn it. If you are not intent on picking up a new paradigm or are not addicted to being constantly confused (me) then you rarely escape that valley.

Yeah, that last part is my concern -- if I can't use FP on a daily basis, is it still worth my time to try learning it?
Yes, it's very mind-expanding, even if you don't use it much in your day-to-day stuff.
map and filter feel quite natural to me, and superior. I like FP but I don't consider myself an "FP person," if only because I don't spend most of my time in it, nor am I particularly well versed in any FP language.

Python occupies an interesting middle ground which might help you see what I mean. I vastly prefer

   filtered_xs = [x for x in xs if x.foo(bar)]
to

   filtered_xs = []
   for x in xs:
     if x.foo(bar):
       filtered_xs.append(x)
If you think of filtering and mapping as trivial operations, it seems like a waste to spend so many words on it as in the latter case. It feels tedious, like filler, since I have to explain how to iterate over a list every time I write a loop. When you can express it crisply and just as precisely in a single line as in the former, the reader is free to occupy their mind with the real work. When you can deal with a higher level of abstraction without introducing a lot of complexity, verbosity, or confusion, I think that's a win.

FTR, the two mainstream languages that frequently emulate this include Ruby (see Array) and, depending on what you're using, JavaScript. If you've played with jQuery, f'rex, you can see this pattern there. (And even monad-like behavior!)

I found maps and filters massively more intuitive than for loops. To be honest, I'm still more likely to screw up a for/while/until loop than I am to mess up using map/filter. Then again, I have a Math background, and Functional Programming is much closer to how Mathematicians think than Imperative Programming IMO.