Hacker News new | ask | show | jobs
by giantDinosaur 2015 days ago
A for loop's greatest strength is its greatest weakness: you can do anything, including in most languages mutating the index variables (god forbid..!), deeply nesting with mutable data, and so on. I usually find I can understand a map quicker than a loop, because a map requires that the code be simpler in most cases.

Obviously in the trivial case though a loop is very easy to understand. The trivial case usually, in my experience, involves iterating through the entirety of an array though anyway, in which case the map is usually more concise.

YMMV! :)

2 comments

Yeah, trivial cases are trivial everywhere.

I like your strength/weakness observation. But if you are mutating index variables, you have a hard case, and it probably will be easier to express with a manual loop than trying to shoehorn it into awkward functional constructs.

An example is the "discard elements by a predicate" function, aka Vec::retain in Rust, std::remove in C++. Rust implements this using a for loop. Maybe it can be done with functional constructs, but it would be harder to write and to understand.

https://doc.rust-lang.org/src/alloc/vec.rs.html#1105

But Vec::retain is pretty close to one of the fundamental functional constructs for containers (it's the in-place version of filter). The argument here is that others should use functions like retain instead of re-implementing that nasty indexing logic. But I don't think it should be surprising that array-like containers are going to have to involve some indexing logic at some level.
Most languages have something like:

    for (ElemType x : collection) {
        # use x here
    }
or:

    for (elem : collection) {
        // use here
    }
for dynamic languages or statically typed ones with lots of inference

Not sure what you can do with this that:

    collections.each(x -> {
        // use here
    })
can't.
Those look like Java loops, and I don't recall them being much more than sugar over a for loop (although presumably they're implementing some Iterable class and apply to more than just arrays).

Either way, like most things in functional programming, it's often about restricting your functions so that they're easier to reason about. There's nothing special about the map function really in any pragmatic sense except when used in conjunction with the other features a good language affords.

I'm also curious if you think something like:

  listOfListsmap : List (List Int) -> List (List Int)
  listOfListsmap =
    (List.map << List.map) (\n -> n + 1)
is easier to understand at a quick glance than a rough equivalent using those loops above:

  collections.forEach(x -> {
    // anything could happen here to x before the inner loop   processes it
    // and since we're probably dealing with mutable variables, the inner loop can
       presumably access things I put here (which may or may not be a problem, but is something you have to think about)
        x.forEach(y -> {
        // do something with y, we can do anything with x *and* y here
        y = y + 1;
        }
    }
knowing that << is the composition function.