New wacky trick for List (In)comprehensions

Many years ago, this would’ve been a tweet, but I don’t really use that platform anymore, so I’ll just post it here.

It is kind of related to these articles that I wrote years ago:

The idea is that, in Erlang, you do not need to have generators on your list comprehensions…

1> WeirdEquals = fun(A, B) -> [equal] =:= [equal || A =:= B] end.
#Fun<erl_eval.43.65746770>
2> WeirdEquals(1,1).
true
3> WeirdEquals(1,2).
false

That, on its own, can be used to write else-less ifs: Instead of…

if A < 0 -> io:format("Hey, ~p is a negative number!", [A]) end

…which will crash if A >= 0, you write…

[io:format("Hey, ~p is a negative number!", [A]) || A < 0]

:warning: Don’t do this at home, kids :warning:

Anyway… Today I found another strange (but clever) thing you can do with List Comprehensions (or with comprehensions in general). You can put a filter before the first generator. For instance, let say you have this problem…

4> Values = fun(MaybeList) -> [V || {K, V} <- MaybeList] end.
#Fun<erl_eval.44.65746770>
5> Values([]).
[]
6> Values([a]).
[]
7> Values([{k1, v1}, {k2, v2}]).
[v1,v2]
8> Values(not_a_list).
** exception error: bad generator not_a_list

And for whatever reason, you want the function to return [] even if what’s given to it is not a list. Of course, you can define your function as…

1> Values = fun(MaybeList) when not is_list(MaybeList) -> [];
               (List) -> [V || {K, V} <- List]
            end.

But… what’s the fun in that?!! Specially when you can also define the function as…

3> Values = fun(MaybeList) -> [V || is_list(MaybeList), {K, V} <- MaybeList] end.
#Fun<erl_eval.42.113135111>
4> Values([]).
[]
5> Values([a]).
[]
6> Values([{k1, v1}, {k2, v2}]).
[v1,v2]
7> Values(not_a_list).
[]

I’m not sure if this is a practice that should be encouraged, but it may be a clever tool to keep around, just in case so I thought I would share it :slight_smile:

14 Likes

I am excited to see what sort of contrived (cursed?) things people can build. As comprehensions become more powerful (e.g. zip comprehensions), I imagine so much more will be possible.

I suppose a small extension to your idea is to make a comprehension that supports either lists or maps:

1> Values = fun(MapOrList) ->
    [V || is_list(List = MapOrList), {_K, V} <- List] ++
    [V || is_map(Map = MapOrList), _K := V <- Map]
  end.
#Fun<erl_eval.42.39164016>
2> Values([]).
[]
3> Values(#{}).
[]
4> Values([{a,1},{b,2}]).
[1,2]
5> Values(#{a => 1, b => 2}).
[1,2]
1 Like