Lists, Maps and Commas

This will be a super-quick post about something that came up at least twice in the last few months within the Erlang community. It’s related to the syntax of maps within a list.

(original image from Good Free Photos)

How Many Maps?

Quick question: how many maps do we have in the following list?

[#{a => 1},
#{b => 2}
#{c => 3}].
  • 1
  • 2
  • 3
  • That doesn’t even compile!

People following the erlang-questions mailing list might know already, but the correct answer is not 3, nor a compile error. It’s 2.

Why not 3?

Well, there is a missing comma in the second row.

But why does it compile, then?

Because what’s written there is a perfectly valid expression, of course. And it matches the syntax used for records, as it’s pointed out in this thread:

-module(sample).

-record (a, {field1, field2}).

-export([bug/0]).

bug() ->
[
#a{field1 = 1, field2 = "foof"} %% COMMA IS MISSING
#a{field1 = 2},
#a{field1 = 3}
].

that function on the shell returns a list with 2 elements, as expected…

1> rr(sample).
[a]
2> sample:bug().
[#a{field1 = 2,field2 = "foof"},
#a{field1 = 3,field2 = undefined}]
3>

What’s going on here?

What these expressions with the missing commas are expressing is understandable. In our example with maps what we are basically doing is:

[#{a => 1}, #{b => 2}#{c => 3}].

It looks weird but, if we would’ve used a variable there…

B = #{b => 2},
[#{a => 1}, B#{c => 3}].

On the other hand, you can’t just put anything on the left of the # sign there, check this out…

3> maps:new() #{b => 2}.
* 1: syntax error before: '#'

In that case, you must use parentheses for this to work…

3> (maps:new())#{b => 2}.
#{b => 2}

So, even when the original expression semantics are understandable, are they useful? Many of us will prefer the compiler/parser to warn us about this or straight up reject the code, but as Jesper L. Andersen points out

In short, regarding this as an invalid expression is to a certain extent
possible, and certainly desirable. But we run into subtle problems when we
want to reject it too, which is what complicates matters.

On the other hand, Dmytro had a great idea: Elvis can check for this. That’s why I opened issue #440 (link below), if you feel like contributing ;)


Bonus Track

While researching for this article I made a couple of extra tests on the console, just to see what would happen if… Check them out:

4> A = #{b => 3} #{b => 4}.
#{b => 4}
5> A.
#{b => 4}
6> (A = #{b => 3}) #{b => 4}.
** exception error: no match of right hand side value #{b => 3}
7> f(A).
ok
8> B = (A = #{b => 3}) #{b := 4}.
#{b => 4}
9> A.
#{b => 3}
10> B.
#{b => 4}

So, in case you were wondering, you can’t use the wacky syntax to do some strange pattern matching. You still have to use parentheses for that.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.