Erlang:变量是未绑定的

时间:2017-09-17 03:50:20

标签: erlang

为什么以下说变量未绑定?

9> {<<A:Length/binary, Rest/binary>>, Length} = {<<1,2,3,4,5>>, 3}.     
* 1: variable 'Length' is unbound

很明显,Length应为3。

我正在尝试使用类似模式匹配的函数,即:

parse(<<Body:Length/binary, Rest/binary>>, Length) ->

但如果失败的原因相同。如何实现我想要的模式匹配?

我真正想要实现的是将传入的tcp流包解析为LTV(长度,类型,值)。

在解析Length和Type之后的某个时刻,我想只准备最多Length个字节作为值,其余的可能是下一个LTV。

所以我的parse_value函数是这样的:

parse_value(Value0, Left, Callback = {Module, Function},
       {length, Length, type, Type, value, Value1}) when byte_size(Value0) >= Left ->
    <<Value2:Left/binary, Rest/binary>> = Value0,
    Module:Function({length, Length, type, Type, value, lists:reverse([Value2 | Value1])}),
    if 
    Rest =:= <<>> ->
        {?MODULE, parse, {}};
    true ->
        parse(Rest, Callback, {})
    end;
parse_value(Value0, Left, _, {length, Length, type, Type, value, Value1}) ->
    {?MODULE, parse_value, Left - byte_size(Value0), {length, Length, type, Type, value, [Value0 | Value1]}}.

如果我可以进行模式匹配,我可以将其分解为更令人愉悦的东西。

4 个答案:

答案 0 :(得分:6)

模式匹配的规则是,如果变量X出现在两个子模式中,如{X,X}或{X,[X]}或类似,则它们必须在两个位置具有相同的值,但每个子模式的匹配仍然在同一输入环境中完成 - 来自一侧的绑定不会转移到另一侧。之后在概念上进行等式检查,就好像你在{X,X2}上匹配并添加了一个保护X =:= X2。这意味着元组中的Length字段不能用作二进制模式的输入,即使你把它作为最左边的元素也不会。

但是,二进制模式中,字段中绑定的变量可以在其后的其他字段中使用,从左到右。因此,以下工作(使用二进制中的前导32位大小字段):

1> <<Length:32, A:Length/binary, Rest/binary>> = <<0,0,0,3,1,2,3,4,5>>.
<<0,0,0,3,1,2,3,4,5>>
2> A.
<<1,2,3>>
3> Rest.                                                               
<<4,5>>

答案 1 :(得分:2)

我之前遇到过这种情况。在二进制语法内部发生的事情与统一(匹配)期间发生的事情之间存在一些奇怪之处。我怀疑只是二进制语法和匹配在VM的某个地方的不同时间发生(我们不知道哪个Length未能被分配 - 也许二进制匹配始终是评估中的第一个,所以Length仍然毫无意义。我曾经深入挖掘并发现,但后来我意识到我从来没有真正需要解决这个问题 - 这可能就是为什么它永远不会被解决&#34;。< / p>

幸运的是,无论你在做什么,这都不会阻止你。

不幸的是,除非你解释你认为这种匹配是个好主意的背景(你有X-Y问题),否则我们无法进一步提供帮助。

在二进制解析中,您始终可以强制执行以下操作之一:

  • 在二进制消息的开头有一个固定大小的标题,告诉你需要的下一个大小元素(从那里可以继续作为一个无限的关联链)
  • 在输入时检查二进制文件以确定您要查找的大小,拉出该值,然后开始真正的解析任务
  • 拥有一组字段,所有字段都符合某些二进制模式标准
  • 将二进制文件转换为列表并使用您可能需要的任意数量的预测和回溯来迭代它

快速解决方案

在不了解您的一般问题的情况下,典型的解决方案如下:

parse(Length, Bin) ->
    <<Body:Length/binary, Rest/binary>> = Bin,
    ok = do_something(Body),
    do_other_stuff(Rest).

但我闻到了一些时髦的东西。

在代码中使用这样的内容几乎总是表示代码结构的更基本方面与您正在处理的数据不一致。

但最后期限。

Erlang是关于满足您在现实世界中的目标的实用代码。考虑到这一点,我建议您暂时执行上述操作,然后返回此问题域并重新考虑它。然后重构它。这将为您带来三个好处:

  1. 有些东西可以立即使用。
  2. 稍后您将学习一些关于解析的基本知识。
  3. 如果您的代码更适合您的数据,那么您的代码几乎肯定会运行得更快。
  4. 示例

    以下是shell中的一个示例:

    1> Parse =
    1>     fun
    1>         (Length, Bin) when Length =< byte_size(Bin) ->
    1>             <<Body:Length/binary, Rest/binary>> = Bin,
    1>             ok = io:format("Chopped off ~p bytes: ~p~n", [Length, Body]),
    1>             Rest;
    1>         (Length, Bin) ->
    1>             ok = io:format("Binary shorter than ~p~n", [Length]),
    1>             Bin
    1>     end.
    #Fun<erl_eval.12.87737649>
    2> Parse(3, <<1,2,3,4,5>>).
    Chopped off 3 bytes: <<1,2,3>>
    <<4,5>>
    3> Parse(8, <<1,2,3,4,5>>).
    Binary shorter than 8
    <<1,2,3,4,5>>
    

    请注意,此版本更安全一些,因为我们可以避免Length比二进制文件长的情况下崩溃。这是另一个很好的理由为什么我们可以在函数头中进行匹配。

答案 2 :(得分:0)

尝试使用以下代码:

{<<A:Length/binary, Rest/binary>>, _} = {_, Length} =  {<<1,2,3,4,5>>, 3}.

答案 3 :(得分:0)

EEP-52中稍微提到了这个问题:

表达式中使用的任何变量都必须事先绑定,或以与表达式相同的二进制模式绑定。也就是说,以下示例是非法的:

illegal_example2(N, <<X:N,T/binary>>) ->
   {X,T}.

并在以下电子邮件中进行了进一步解释:http://erlang.org/pipermail/eeps/2020-January/000636.html

非法。除了一个例外,匹配不是从左到右进行的 顺序,但模式中的所有变量都将绑定在同一位置 时间。这意味着变量必须在匹配之前绑定 开始。对于地图,这意味着键中引用的变量 表达式必须在匹配的大小写(或接收)之前绑定 地图。在函数头中,所有映射键必须为文字。

此一般规则的例外是在二进制模式中, 这些段从左到右匹配,并且变量 前一个段可以在段的大小表达式中使用 在二进制模式的后面。

OTP团队的一位成员也提到他们制造了可以做到这一点的原型,但是它从未完成http://erlang.org/pipermail/erlang-questions/2020-May/099538.html

实际上,我们试图使您的示例合法。转型 我们执行的代码不是为了重写警卫,而是为了匹配 参数或参数的部分以正确的顺序排列,以便变量 输入变量将在使用前绑定。 (我们会做一个 拓扑排序以找到正确的顺序。)例如, 转换看起来像这样:

legal_example(Key, Map) ->
 case Map of
    #{Key := Value} -> Value;
   _ -> error(function_clause, [Key, Map])
 end.

在原型实现中,编译器可以编译 以下示例:

convoluted(Ref,
       #{ node(Ref) := NodeId, Loop := universal_answer},
       [{NodeId, Size} | T],
       <<Int:(Size*8+length(T)),Loop>>) when is_reference(Ref) ->
    Int.

当重复变量时,事情开始崩溃。重复 模式中的变量在Erlang中已经有含义(它们应该是 相同),因此难以区分 绑定变量或使用二进制大小或映射的变量 键。这是原型无法处理的示例:

foo(#{K := K}, K) -> ok.

人们可以看到应该像这样进行改造:

foo(Map, K) ->   case Map of
    {K := V} when  K =:= V -> ok   end.

这里还有其他几个可行的示例,但原型可以 拒绝编译(通常会发出无法理解的错误消息):

bin2(<<Sz:8,X:Sz>>, <<Y:Sz>>) -> {X,Y}.

repeated_vars(#{K := #{K := K}}, K) -> K.

match_map_bs(#{K1 := {bin,<<Int:Sz>>}, K2 := <<Sz:8>>}, {K1,K2}) ->
Int.

另一个问题是当示例被正确拒绝时,该错误 消息会令人困惑。

由于显然需要进行更多工作,因此我们将 现在的想法。就我个人而言,我不确定这个想法在 第一名。但我确信一件事:实施将是 非常复杂。

UPD:2020-05-14的最新消息