@RichardC, спасибо!

-module(count).
-export([count/1]).

count(L) when is_list(L) ->
  do_count(L, #{});
count(_) ->
  error(badarg).

do_count([], Acc) -> Acc;
do_count([H|T], #{}) -> do_count(T, #{ H => 1 });
do_count([H|T], Acc = #{ H := C }) -> do_count(T, Acc#{ H := C + 1});
do_count([H|T], Acc) -> do_count(T, Acc#{ H => 1 }).

м примере третье предложение, где ключ карты «H» существует и имеет связанный с ним счет, не будет компилироваться. Компилятор жалуется:

count.erl:11: variable 'H' is unbound    

Почему H не связан?

Это работает, кстати:

do_count([], Acc) -> Acc;
do_count([H|T], Acc) -> do_count(T, maps:update_with(H, fun(C) -> C + 1 end, 1, Acc)).

Но похоже, что образец соответствуетдолжен работать, а это не так.

 zxq923 сент. 2017 г., 13:06
Это много подходит. Если у вас есть дополнительные вопросы об этом, пожалуйста, посетитеErlang / OTP чат и спросите, чтобы я мог обновить свой ответ. Между отличным ответом Ричарда и моим собственным, более объяснительным / исследовательским, я хочу создать каноническую ссылку на этой странице вопросов для этой проблемы, когда бы она ни возникала позже (особенно на ML и IRC).

Ответы на вопрос(3)

Решение Вопроса

https://stackoverflow.com/a/46268109/240949.

Когда вы используете одну и ту же переменную несколько раз в шаблоне, как в этом случае с H:

do_count([H|T], Acc = #{ H := C }) -> ...

семантика сопоставления с образцом в Erlang говорит, что это как если бы вы написали

do_count([H|T], Acc = #{ H1 := C }) when H1 =:= H -> ...

то есть они сначала связаны отдельно, а затем сравниваются на равенство. Но ключ в шаблоне карты должен быть известен - он не может быть такой переменной, как H1, а следовательно, и ошибкой (точно так же, как для спецификаторов размера поля в двоичных шаблонах, в ответе, с которым я связан).

Основное различие в этом вопросе состоит в том, что у вас есть заголовок функции с двумя отдельными аргументами, и вы можете подумать, что сначала нужно сопоставить шаблон [H | T], привязав H до того, как будет пробован второй шаблон, но такого порядка нет гарантия; это как если бы вы использовали один аргумент с шаблоном кортежа {[H | T], # {H: = C}}.

 zxq923 сент. 2017 г., 12:42
Ах да Тот ответ, который вы дали вчера, тот же самый, который я связал в своем (гораздо более многословном) ответе. Мне нужно добавить это конкретное объяснение в закладки, так как оно такf(A, #{A := _}) приравнивая при объединении кf(A, #{B := _}) when B =:= A, Дополнительной загадкой для новичков является то, что этоделает работать вcase, Это также озадачивает меня, если я перестаю думать о том, чего я не склонен делать, потому что я простоиспользуемый чтобы это было так. Это и я не использую карты повсюду. ;-)
 Mark Allen25 сент. 2017 г., 10:51
Спасибо за отличное и понятное объяснение!

при использовании карт в заголовке функционального предложения порядок соответствия не гарантируется, В результате в вашем примере вы не можете рассчитывать на[H|T] соответствовать, чтобы обеспечить значение дляH.

Некоторые функции карт выглядят так, как будто они должны работать, и Джо Армстронг говорит, что они должны работать, но это не так. Это тупая часть эрланга. Засвидетельствуйте мое недоверие здесь:https://bugs.erlang.org/browse/ERL-88

Более простые примеры:

do_stuff(X, [X|Y]) ->
    io:format("~w~n", [Y]).

test() ->
    do_stuff(a, [a,b,c]).

4> c(x).
{ok,x}

5> x:test().
[b,c]
ok

Но:

-module(x).
-compile(export_all).

do_stuff(X, #{X := Y}) ->
    io:format("~w~n", [Y]).

test() ->
    do_stuff(a, #{a => 3}).


8> c(x).
x.erl:4: variable 'X' is unbound
 7stud23 сент. 2017 г., 11:46
@RichardC, спасибо!
 RichardC23 сент. 2017 г., 10:35
Речь идет не об особенностях карт, а о правилах языка для привязки переменных в сопоставлении с образцом. Смотрите мой ответ для деталей.

На самом деле, хотя это явно не запрещает это в документах, документахделать явно заявить только, чтосоответствуетлитералы будет работать в функциональных главах, Я считаю, что предпринимаются усилия, чтобы сделать это строительство, но пока нет.

Проблемы, связанные с назначением VS унификации в различных контекстах в функциональных главах, связаны с другим вопросом осопоставление значений внутреннего размера в двоичных файлах в функциональных заголовках это пришло на днях.

(Помните, глава функции не просто выполняет присваивание, этотакже пытается эффективно выбрать путь исполнения, Так что это на самом деле не простая проблема.)

Все, что сказал, более Erlangish (и более простая) версия вашегоcount/1 функция может быть:

count(Items) ->
    count(Items, #{}).

count([], A) ->
    A;
count([H | T], A) ->
   NewA = maps:update_with(H, fun(V) -> V + 1 end, 1, A),
   count(T, NewA).

Случай, против которого вы пишете, был предвиден stdlib, и у нас есть отличное решение в модуле карт под названиемmaps:update_with/4.

Обратите внимание, что мы не назвалиcount/2 новое имя. Если в программе нет необходимости, обычно проще назвать вспомогательную функцию с другой арностью одним и тем же при выполнении явной рекурсии. Идентификатор функцииName/ArityТаким образом, это две совершенно разные функции, независимо от того, является ли метка одинаковой. Также обратите внимание, что мы не проверяли тип аргумента, потому что у нас есть явное совпадение вcount/2 этоможет только соответствовать списку и так броситbad_arg исключение в любом случае.

Иногда вам понадобятся полиморфные аргументы в Erlang, и проверка типов уместна. Выпочти никогда не хочу защитный код в Эрланге.

Сессия с модулем под названиемfoo:

1> c(foo).
{ok,foo}
2> foo:count([1,3,2,4,4,2,2,2,4,4,1,2]).
#{1 => 2,2 => 5,3 => 1,4 => 4}

НО

Мы хотим избежать явной рекурсии, если нет вызова для нее, поскольку у нас есть все эти изящные функциональные абстракции, лежащие в stdlib. Что вы действительно делаете, так это пытаетесь сжать список значений в произвольно агрегированнуюодно значение и это по определениюскладка, Таким образом, мы могли бы переписать вышесказанное, возможно, более идиоматически, как:

count2(Items) ->
    Count = fun(I, A) -> maps:update_with(I, fun(V) -> V + 1 end, 1, A) end,
    lists:foldl(Count, #{}, Items).

И мы получаем:

3> foo:count2([1,3,2,4,4,2,2,2,4,4,1,2]).
#{1 => 2,2 => 5,3 => 1,4 => 4}

относительноcase...

То, что я написал об объединении в функции, гласит:для функциональных головок потому что они являются абсолютно пустым контекстом объединения. Ответ Ричарда - лучший способ вспомнить, почему это безумие:

f(A, #{A := _})

эквивалентно

f(A, #{B := _}) when B =:= A

И это просто не полетит. Его сравнение с соответствием кортежам точно.

...но...

Вcase где первичные объекты уже были назначены это все работает просто отлично. Потому что, как любезно упомянул Ричард в комментарии,здесь только одинA вcase ниже.

1> M = #{1 => "one", 2 => "two"}.
#{1 => "one",2 => "two"}
2> F = 
2>   fun(A) ->
2>     case M of
2>       #{A := B} -> B;
2>       _         -> "Oh noes! Not a key!"
2>     end
2>   end.
#Fun<erl_eval.6.87737649>
3> F(1).
"one"
4> F(2).
"two"
5> F(3).
"Oh noes! Not a key!"

Так что это может показаться немного своеобразным, но это имеет смысл на основе правил сопоставления / объединения. А значит, вы можете написать свойdo_count/2 как вы сделали выше, используяcase внутри функции, но не как набор функциональных головок.

 RichardC23 сент. 2017 г., 18:44
В случае случая (так сказать) проблем нет, потому что шаблон в забавной голове содержит только один экземпляр A, шаблон сопоставляется и выбирается правильное предложение (если было несколько предложений). Затем оценка тела предложения продолжается с привязкой. При рассмотрении предложений случая A является известным объектом и может использоваться в качестве ключа в шаблоне карты.

Ваш ответ на вопрос