2

Here is an example from the "Programming with Erlang" (2nd edition):

count_chars([], Result) ->
    Result;
count_chars([C|Substring], #{C := N}=Result) ->
    count_chars(Substring, Result#{C := N+1 });
count_chars([C|Substring], Result) ->
    count_chars(Substring, Result#{C => 1}).

..which mercylessly yields the following error:

variable 'C' is unbound

So I am kind of stuck here; to my view, variable 'C' is bound, namely it must be a head of the string (just a linked list of chars, right?). Yet Erlang disagrees with me, breaking example from the (probably, outdated?) book I'am reading right now.

So what's wrong? What's the right way to pattern-match in this particular example?

P.S. A screenshot from the book. Pay attention at slightly different syntax, which also doesn't work for me: enter image description here

P.P.S. I am using the latest version of Erlang I've managed to download from the official site.

choroba
  • 231,213
  • 25
  • 204
  • 289
Zazaeil
  • 3,900
  • 2
  • 14
  • 31
  • 1
    Pattern matching doesn't work on maps with variables, see https://stackoverflow.com/a/34810812/1030675 and https://stackoverflow.com/a/59148351/1030675. – choroba Dec 17 '19 at 12:36
  • 2
    The book contains a lot of wishful thinking about how maps should work in erlang. Consequently, I found that section of the book to be extremely frustrating. If I remember correctly, the explanation was that the order that the patterns are matched is not known, and when you write `[C|Substring], #{C := N}=Result` you are assuming that the pattern `[C|Substring]` will be matched first, so that C is bound in the second pattern. But, if the patterns are matched in the opposite order, then C will be unbound in the map pattern, hence the error message. – 7stud Dec 17 '19 at 17:08
  • So here is the answer: [link to another SO answer](https://stackoverflow.com/a/27803258/7493840). – Zazaeil Dec 17 '19 at 20:37
  • `#{C := N}=Result` didn't work with the first release of OTP introducing the maps data structure, but it is available now. – Pascal Dec 18 '19 at 14:44

1 Answers1

3

C must be bound before the expression #{C := N}=Result is evaluated.

You consider that C is bound since the first parameter [C|Substring] was evaluated before: #{C := N}=Result. In fact it is not the case. There is no real assignment until a head evaluation succeed and the function enters the body.

Writing count_chars([C|Substring], #{C := N}=Result) -> is exactly the same as count_chars([C1|Substring], #{C2 := N}=Result) when C1 =:= C2 ->

During the head evaluation, each element is stored in a different element (a place in the heap) to check if all the parameters match the head definition. In your case the compiler want store the value C in an element, let's say x1 and the key C? in another element, let's say x2, and then verify that x1 and x2 are equals. the second operation is not possible without a deep modification of the compiler behavior.

I wrote a small example to show how it works, and compiled it with the option 'S' to see the result of the compilation:

test([K|_],K,M) -> % to see how the test of parameter equality is done
     #{K := V} = M, % to verify that this pattern works when K is bound.
     V.

the assembly result is :

{function, test, 3, 33}.
  {label,32}.
    {line,[{location,"mod.erl",64}]}.
    {func_info,{atom,mod},{atom,test},3}.
  {label,33}.
    {test,is_nonempty_list,{f,32},[{x,0}]}.               % the whole list is assigned to x0
    {get_hd,{x,0},{x,3}}.                                 % the list's head is assigned to x3
    {test,is_eq_exact,{f,32},[{x,1},{x,3}]}.              % the second parameter K is assigned to x1, verify if x1 =:= x3
    {test,is_map,{f,34},[{x,2}]}.                         % the third parameter M is assigned to x2, check if it is a map if not go to label 34
    {get_map_elements,{f,34},{x,2},{list,[{x,3},{x,0}]}}. % get the value associated to key x3 in the map x2 and store it into x0, if no suck key go to label 34
    return.
  {label,34}.                                             % throw a badmatch error
    {line,[{location,"mod.erl",65}]}.
    {badmatch,{x,2}}.

Now, to code your function you can simply write:

count_chars([], Result) ->
    Result;
count_chars([C|Substring], Result) ->
    N = maps:get(C, Result, 0) +1,
    count_chars(Substring, Result#{C => N }).
Pascal
  • 13,977
  • 2
  • 24
  • 32