-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)).
但似乎模式匹配应该才能工作,而且它没有。
答案 0 :(得分:10)
答案与我最近给出的答案几乎相同: 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}}的参数一样。
答案 1 :(得分:3)
因为这种匹配在统一的上下文之外发生。事实上,虽然它没有在文档中明确禁止这一点,但文档明确仅表明matches with literals will work in function heads。我相信正在努力使这项建设工作,但还没有。
围绕职能部门内不同背景下的统一VS分配的问题与前几天出现的另一个关于matching internal size values within binaries in function heads的问题有关。
(请记住,函数头不仅仅是在进行赋值,而且还试图有效地选择执行路径。所以这实际上并不是一个简单的问题。)
所有这一切,你的count/1
函数的更多Erlangish(和更简单)版本可能是:
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
命名为新名称。除非在程序中有必要,否则在执行显式递归时,通常更容易将具有不同arity的辅助函数命名为相同的东西。函数的标识是Name/Arity
,因此无论标签是否相同,它们都是两个完全独立的函数。另外,请注意我们没有检查参数类型,因为我们在count/2
中有一个显式匹配,只能匹配列表,因此无论如何都会抛出bad_arg
异常
有时你会想要在Erlang中使用多态参数,并且类型检查是合适的。但是,几乎从不想在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}
<强> BUT 强>
我们希望避免显式递归,除非有调用它,因为我们在stdlib中有所有这些漂亮的listy函数抽象。你真正在做的是尝试将值列表压缩成任意聚合的单值,根据定义,它是 fold 。因此,我们可以将上述内容重写为:
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
...
我在函数头中写的关于统一的内容是 - for function head ,因为它们是一个完全空白的统一上下文。理查德的答案提供了记住这个疯狂原因的最佳简写:
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
,而不是作为一组函数头。
答案 2 :(得分:-1)
我为自己制定了这条规则:在函数子句的头部使用map时,不保证匹配的顺序。因此,在您的示例中,您无法依靠[H|T]
匹配为H
提供值。
地图的几个特征看起来应该有效,而Joe Armstrong说他们应该工作,但他们不会。它是erlang的一个愚蠢的部分。在这里见证我的怀疑: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