[erlang-questions] count_characters example from Programming Erlang

Joe Armstrong erlang@REDACTED
Fri Feb 5 09:24:34 CET 2016

On Fri, Feb 5, 2016 at 5:58 AM, zxq9 <zxq9@REDACTED> wrote:
> On 2016年2月4日 木曜日 20:59:47 Mark Bucciarelli wrote:
>> Hi,
>> I'm trying to compile the count_characters example from Programming Erlang
>> book.
>> But I get an error, and I can't see a typo.
>> -module(t).
>> -export([count_characters/1]).
>> 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.
>> 1> c(t).
>> t.erl:7: variable 'H' is unbound
>> error
>> 2>
>> This behavior contradicts the point made in the text:
>>         There are two things to note about count_characters/2.
>>         In the first clause, the variable H inside the map is also
>>         defined outside the map and thus is bound (as required).
>>         In the second clause, we use map_extend to add a new
>>         key to the map.
> Someone will be along to sharpshoot this statement, but I'm pretty sure
> that the book was written based on the proposed map implementation, and
> the actual implementation turned out to not allow match-assignment on keys.
> I believe variable map keys are permitted in R18, but not sure about this
> particular case.
> A working version can be built this way, though:
>   -module(foo).
>   -export([count_characters/1]).
>   count_characters(Str) ->
>       count_characters(Str, #{}).
>   count_characters([Char | Rest], Counts) ->
>       Count = maps:get(Char, Counts, 0),
>       count_characters(Rest, maps:put(Char, Count + 1, Counts));
>   count_characters([], Counts) ->
>       Counts.
> Same principle, just slightly different way of going about it.
> The difficulty of writing a book against an unimplemented and still
> shifting standard is daunting. I'm amazed he was even able to!

The idea was that by the time the book was published the
book and implementation would be in phase with each other.

There's a back story here:

In the early days of Erlang the implementation and the documentation
were always out of phase. This means there was a heck of a lot of code
and very little documentation.

When the documentation was written there was short time when it was in
phase with the code but then the code changed and the documentation
was not updated. The point is the two were never in phase, or at least
if there were in phase it was for a short time.

At the time I used to say

    "If the code and documentation different, read the code."

But reading code is difficult - especially for a beginner.

Anyway even if I can understand the code I don't know a) what it's
supposed to do and b) if it is buggy.

When the code and documentation differ I don't know which is definitive.

At a certain point in time I thought to myself - "this is stupid" so I
changed strategy.

I said -

    "the documentation should describe that the system is
      supposed to do, and if the code and documentation differ it's a bug
      and the code is wrong."

Then I wrote the documentation from the standpoint of what the system
should do rather than what it actually does, and I do this in my

Most system documentation describe what the code does, and not what it
should do.

In an ideal world the two should be the same - but this is only true
in very mature systems.

Now to the point in question. If stare hard at the clause where the problem is
it looks like this:

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

Erlang semantics dictate that if a variable occurs more than once in a
pattern then it must have the same value - so reading from left to
right H has a value after matching the first argument in the function
and so this value can be used as a key in the hash map lookup that is
implies by the second argument.

This should be similar to the behavior of pattern matching in binaries
where you can "propagate forward" a length of a segment, which has been
pattern matched earlier in the binary.

For example:

     <<Len:8, A:Len/binary, B/binary>> = <<2,1,2,3,4,5>>

matches with bindings Len = 2, A = <<1,2>> and B = <<3,4,5>>
So Len is propagated forwards in the pattern match (in this case, and in the
hashmap case we can't match all the arguments "in parallel" but have to do
it in a specific order.

To compile hashmap patterns like the one above that failed, is
certainly possible.


> -Craig
> _______________________________________________
> erlang-questions mailing list
> erlang-questions@REDACTED
> http://erlang.org/mailman/listinfo/erlang-questions

More information about the erlang-questions mailing list