2

My question is related to an existing question (and boy, was I surprised to see it was a known bug in Erlang itself!). I'm using the same count_characters example from the book, and getting the same error even though I'm on R19.

Code:

% Frequency count of characters in a string
count_characters(Str) ->
    count_characters(Str, #{}).

count_characters([H|T], #{ H => N }=X) ->
    count_characters(T, X#{ H := N+1 });
count_characters([H|T], X) ->
    count_characters(T, X#{ H => 1 });
count_characters([], X) -> X.

and the error:

1> c(lib_misc).
lib_misc.erl:40: illegal pattern
lib_misc.erl:41: variable 'N' is unbound
error

Here line 40 refers to the first clause of count-characters/2.

My questions are:

  • I'm not able to understand what exactly the bug is from the linked SO question. Can someone please describe in simple terms which variable is causing the error and why?
  • Is this still not fixed in R19?? If not, when will it be? It's sad to see the author's book providing an example that is so badly broken.

I can see the accepted answer on the linked page uses stuff like maps:update. I could do the same, but I'd first like to know why the error exists.

Community
  • 1
  • 1
ankush981
  • 5,159
  • 8
  • 51
  • 96

1 Answers1

3

The current error you see doesn't come from an Erlang bug. => is used for constructing maps, := for pattern matching (both are allowed for updating, the difference is that := only works for keys already in the map and => allows adding new keys). So you need := in the pattern:

count_characters([H|T], #{ H := N }=X) ->
    % H => N+1 is also legal here, and means the same because we know H is a key of X
    count_characters(T, X#{ H := N+1 }); 

However, after you fix this you do run into the problem: H isn't bound yet in the #{ H := N } pattern, and this isn't currently supported. This could be fixed by matching multiple argument patterns in order, so that H gets bound by [H|T]. This isn't done in R19B (at least, judging from this example) and I don't know if there are any plans to change this. It makes sense to me personally that the patterns are checked simultaneously, so I am not even sure this change would be desirable.

You can work around this by combining the first two clauses and matching X in the body instead of the head:

count_characters([H|T], X) ->
    case X of 
        #{ H := N } -> count_characters(T, X#{ H => N+1 });
        _ -> count_characters(T, X#{ H => 1 })
    end;
count_characters([], X) -> X.
Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
  • Ummm . . . Isn't `:=` used for updating the keys in a map and using them for another map? Sounds more like construction than pattern matching to me (or maybe not, but certainly there is construction involved). – ankush981 Jul 28 '16 at 08:20
  • And regarding `H` not being bound in `#{ H := N }`, the second edition of the book notes specifically: "In the first clause, the variable H inside the map is also defined outside the map and thus is bound (as required)". Seriously, all this confusion is quite demoralizing. :( – ankush981 Jul 28 '16 at 08:22
  • 2
    Updating maps allows both `=>` and `:=` (the difference is that `:=` only works for keys already in the map, `=>` allows adding new keys). But `=>` can't be used in patterns. I fixed the answer's wording a bit. – Alexey Romanov Jul 28 '16 at 08:28
  • Thanks. So my conclusion is there was a type in the book, where `=>` and `:=` seem to have swapped places. But beyond that, I'm very bitter about the author's (mistaken) confidence in bound variables while matching maps. – ankush981 Jul 28 '16 at 08:30
  • 1
    For the second the question is whether patterns for multiple arguments are matched simultaneously or left-to-right. It isn't specified in Erlang's reference manual, and I can't think of any other case where it would matter. – Alexey Romanov Jul 28 '16 at 08:41