Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

A for loop is a general tool. It can be used for anything. Because of that, it conveys little or no information. As a reader I have to read the loop in detail to see that what it does is perform a mapping, and not something slightly different (the last item could be skipped, etc etc).

The expressiveness of higher order functions comes not from terseness (only) but by expressing intent more clearly.

E.g this expresses (in pseudo) A conversion followed by a filter.

> odd_ints = strings.map(parseInt).select(odd)

This was expressed in the forum, but there was no agreement that this was readable, so I fully expect you to also think the for loop to be more readable. I suspect there is a divide between those who prefer reading how something is done rather than what is intended, in order to understand it.



Ok, that's a good example. Your one-liner is pretty concise and the intent is clear.

I think on a higher level with complex code, you definitely want better abstractions to convey intent, rather than say just having one large function that does everything.

I think in many cases map/filter does in fact help with readability of intent, especially if you're not declaring the function body inline, and the functions are commonly available ones like parseInt. But generally this isn't the case, which diminishes the readability such that it's comparable to a for-loop anyways.

I spend most of my time trying to figure out exactly that -- the higher level abstractions that aren't easily conveyed by "map" or "filter", that having map/filter generic functions isn't high on priority. I just want a simple language that I can spew out thoughts onto uncompilable code... tweak the code often as I see fit, and work on fixing type errors later and once I'm done with that it will probably run fine with few bugs. The nice thing about for-loops is that it exposes more control points e.g. breaking out early, using the index, inserting log lines -- that the flexibility helps me mutate the code quickly.

So maybe it's more about coding style, rather than readability. Do you like to spec out your code completely before writing things down, or do you prefer to define the spec as you write and edit the code because it helps you get things done faster?


Another data point for you (I'm not the grandparent poster):

I prefer to write exploratory code and spec out the design as I go along, and I also prefer map/filter (or list comprehensions) to for-loops.

I suspect it does have to do with coding style, but not in the way you suspect. I like to write very small functions - often one-liners, rarely more than a page - and have each function do one thing and one thing only. I also tend to code mostly bottom-up, figuring out what abstractions I need, writing them, and then writing the functions that use them. So I almost never use an inline lambda for a map, it's usually a function I've already defined.

All of the exploratory scenarios you list are handled by built-in functions in my language of choice (Python). Breaking out early = itertools.takewhile(). Using the index = enumerate(). Inserting the log lines, I'd just insert them in the mapper (although there's also trace).


Is there a reason you need some built-in map thing and not just user-defined functions? If a loop is too hard to read inline, you slap it in a nicely named function and now it's more clear and more concise. Either way, you have to write the code to do the conversion. select(odd) doesn't work unless you've already written the code behind whatever "odd" is, for example.

I can write go code that makes this line legal:

    odd_ints := parseInts(myStrings).select(odd)
but I'd probably write code so it looked like this:

    odd_ints := odds(parseInts(myStrings))
Is either of these harder to read? Does it matter that parseInts is a "map" and odds is a "filter"? Their function is obvious by their names. If anything, the words "map" and "filter" are extraneous.


they are so pervasive/important that it should be included in the core library. I'd include my own collection_utils every single time.

That said, having a library function of course requires it to work in a type safe way for all collections/functions, which might make this argument really be one about generics and not about two collection functions. If this is omitted because generics is, I think it's just another argument why omitting generics is making the language simple to the point of being stupid.


Instead of map/filter:

    // using some fake filter/map/lambda syntax
    names := machines.filter(strings.HasPrefix(m.tag, "ec2")).map(|m| = m.name)
why not just write

    names := []string{}
    for _, m := range machines {
        if strings.HasPrefix(m.tag, "ec2") {
            names = append(names, m.name)
        }
    }
What happens when you read that first implementation? Don't you read it as "for each machine, if its tag has the prefix "ec2", then append its name to the list that is returned? Isn't that the exact same thing you'd read the second one as? Except that the second one requires no special knowledge other than loops and if statements.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: