[erlang-questions] Request feedback on example project

Richard A. O'Keefe <>
Thu Feb 4 22:30:28 CET 2016


On 5/02/16 9:09 am, Siraaj Khandkar wrote:
> (1) surprising filter semantics:
>
> 1> L = [{foo, a}, {bar, b}].
> [{foo,a},{bar,b}]
> 2>
> 2>  [X || {foo, X} <-L].
> [a]
> 3>

This is how list comprehensions are supposed to work.
What's surprising about that?
> While lists:map/2 crashes, as expected:
3> lists:map(fun ({foo, X}) -> X end, L).

Yes, but that is not a translation of the list comprehension
and the list comprehension is not a translation of this.
They are different, and that is one important reason to
prefer the list comprehension.  To get the effect of the
list comprehension -- which is a commonly desired effect --
you would have to write

lists:flatmap(fun ({foo,X}) -> [X]
                         ; (_)         -> []
                     end, L).

which is a bit confusing because there isn't any useful
connection between flatmap and flatten.

To translate your map call as a list comprehension,
F = fun ({foo,X}) -> X end, [F(X) || X <- L].

> There're certainly ways around that, such as:
Why even think about "ways around that"?  List comprehensions
and calls to map are both doing their intended, documented,
and different jobs.
>
> 4>  [begin {foo, Foo} = X, Foo end || X <- L].
> ** exception error: no match of right hand side value {bar,b}
> 5>
> 5>  [(fun ({foo, Foo}) -> Foo end)(X) || X <- L].
> ** exception error: no function clause matching 
> erl_eval:'-inside-an-interpreted-fun-'({bar,b})
> 6>
>
> But all that feels like extra gymnastics to just use list 
> comprehensions for the sake of using list comprehensions.

No, it's extra gymnastics in the service of a straw man argument.
In real code, I *want* the filtering behaviour of list comprehensions,
so I use them instead of switching over to bulky calls to flatmap/2.

> (2) Explicitly-declared intention. When you explicitly call "filter" 
> and "map" - I have an immediate high-level idea of where you're going 
> with it, but list comprehensions tempt one to conflate things, so it 
> is harder to understand the intention (there're some gnarly ones out 
> in the wild...).

It is possible to write unreadable tangles of code using filter and map 
as well.

One thing I would mention is the linguistic concept of "topicalisation",
the way that people can shuffle different parts of a sentence to the
beginning to highlight it.  In

     [hypot(X, Y) || {X,Y} <- zip(Xs, Ys)]

the topic is hypot(X, Y): we are invited to focus on the *result*.
In
     map(zip(Xs,Ys), fun ({X,Y}) -> hypot(X, Y) end)

the topic is zip(Xs, Ys): we are invited to focus on the *inputs*, not
on what happens to them.

Looking at it from this viewpoint, it is immediately obvious that
*SOME* expressions will be more intention-revealing when written
with the inputs first so that map or flatmap + filter is a good fit
*SOME* expressions will be more intention-revealing when written
with the results first.

It's just like the way Haskell has *both*
    let Pattern = Expression in Result
and
    Result where Pattern = Expression

The fact that something is a call to map or a call to filter is NOT
intention-revealing as such.  All that tells me is the what, not the
why, and the why is the intention.

It is good that Erlang has *both* list comprehensions *and* higher-
order functions.  Do not blame either for not being identical to the
other.


More information about the erlang-questions mailing list