Erlang: variable is unbound

Viewed 2834

Why is the following saying variable unbound?

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

It's pretty clear that Length should be 3.

I am trying to have a function with similar pattern matching, ie.:

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

But if fails with the same reason. How can I achieve the pattern matching I want?


What I am really trying to achieve is parse in incoming tcp stream packets as LTV(Length, Type, Value).

At some point after I parse the the Length and the Type, I want to ready only up to Length number of bytes as the value, as the rest will probably be for the next LTV.

So my parse_value function is like this:

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]}}.

If I could do the pattern matching, I could break it up to something more pleasant to the eye.

4 Answers

This question is mentioned a bit in EEP-52:

Any variables used in the expression must have been previously bound, or become bound in the same binary pattern as the expression. That is, the following example is illegal:

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

And explained a bit more in the following e-mail: http://erlang.org/pipermail/eeps/2020-January/000636.html

Illegal. With one exception, matching is not done in a left-to-right order, but all variables in the pattern will be bound at the same time. That means that the variables must be bound before the match starts. For maps, that means that the variables referenced in key expressions must be bound before the case (or receive) that matches the map. In a function head, all map keys must be literals.

The exception to this general rule is that within a binary pattern, the segments are matched from left to right, and a variable bound in a previous segment can be used in the size expression for a segment later in the binary pattern.

Also one of the members of OTP team mentioned that they made a prototype that can do that, but it was never finished http://erlang.org/pipermail/erlang-questions/2020-May/099538.html

We actually tried to make your example legal. The transformation of the code that we did was not to rewrite to guards, but to match arguments or parts of argument in the right order so that variables that input variables would be bound before being used. (We would do a topological sort to find the correct order.) For your example, the transformation would look similar to this:

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

In the prototype implementation, the compiler could compile the following example:

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

Things started to fall apart when variables are repeated. Repeated variables in patterns already have a meaning in Erlang (they should be the same), so it become tricky to understand to distinguish between variables being bound or variables being used a binary size or map key. Here is an example that the prototype couldn't handle:

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

A human can see that it should be transformed similar to this:

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

Here are few other examples that should work but the prototype would refuse to compile (often emitting an incomprehensible error message):

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.

Another problem was when example was correctly rejected, the error message would be confusing.

Because much more work would clearly be needed, we have shelved the idea for now. Personally, I am not sure that the idea is sound in the first place. But I am sure of one thing: the implementation would be very complicated.

UPD: latest news from 2020-05-14

Related