map pattern matching in Erlang, unexpected error (

2019-07-24 20:04发布

问题:

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.

回答1:

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).


回答2:

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.