1

The code below is basically copied from J.A.'s book:

-module(mapz).
-export([count_chars/1]).

count_chars(Str) ->
    count_chars(Str, #{}).

count_chars([H|T], #{H := N}=X) -> % line that throws
    count_chars(T, X#{H := N+1});
count_chars([H|T], X) ->
    count_chars(T, X#{H => 1});
count_chars([], X) -> X.

however, compiling it in the shell gives me

151> c(mapz).
mapz.erl:7: variable 'H' is unbound
error
152>

I understand the importance of having H bound before it can be used for matching a key in a map; and, as far as I can tell, it is being matched to the head of the list(string) in the first argument, and is therefore bound by the time the second argument (matching against the map) is evaluated. More to the point, the example being from the book, I suspect it to be correct. The book seems to be using OTP17, however, and I'm now on 20, wonder if things have changed? But which things?

Thank you for your time.

alexakarpov
  • 1,848
  • 2
  • 21
  • 39

2 Answers2

2

in the clause count_chars([H|T], #{H := N}=X) -> ... the compiler does not consider that H has been bound during the pattern matching of the first parameter : [H|T], so it is unable to pattern match the second parameter #{H := N} (I think it could be possible with the actual assembly, see my answer to this topic)

But there is the function you need in the maps library:

count_chars(Str) ->
    count_chars(Str, #{}).

count_chars([H|T],X) ->
    count_chars(T, maps:update_with(H,fun(V) -> V+1 end, 1,X));
count_chars([], X) ->
    X.

see documentation at Maps erlang doc

even shorter using the lists:foldl/3

count_chars(Str) -> 
    lists:foldl(fun(C,Map) ->  maps:update_with(C,fun(Count) -> Count+1 end, 1,Map) end, #{},Str).
Pascal
  • 13,977
  • 2
  • 24
  • 32
  • Thank you; I was contemplating the very say thought: "Hm, I wonder how does the compiler work, will it be able to 'recognize' the H from the first argument, and use it in binding in the second parameter?" Never wrote a compiler in my life, but this wasn't something to be taken for granted, yeah. – alexakarpov May 30 '17 at 01:54
1

You can't do pattern matching (at least not yet) on a Map with a key being a variable, like:

count_chars([H|T], #{H := N}=X)

but this would work:

count_chars([H|T], #{"MyKey" := N}=X)

you can update the code to be like:

-module(mapz).
-export([count_chars/1]).

count_chars(Str) ->
  count_chars(Str, #{}).

count_chars([H|T], X) ->
  case maps:is_key(H,X) of
    false -> count_chars(T, X#{ H => 1 });
    true  -> #{ H := Count } = X,
             count_chars(T, X#{ H := Count+1 })
    end;
count_chars([], X) ->
  X.
rorra
  • 9,593
  • 3
  • 39
  • 61
  • Makes me wonder how it is that Joe put the example in the book, which doesn't actually work. I wonder if they rolled back a feature or something... – alexakarpov May 30 '17 at 02:04