Erlang: variable is unbound

2019-06-21 22:21发布

问题:

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.

回答1:

The rules for pattern matching are that if a variable X occurs in two subpatterns, as in {X, X}, or {X, [X]}, or similar, then they have to have the same value in both positions, but the matching of each subpattern is still done in the same input environment - bindings from one side do not carry over to the other. The equality check is conceptually done afterwards, as if you had matched on {X, X2} and added a guard X =:= X2. This means that your Length field in the tuple cannot be used as input to the binary pattern, not even if you make it the leftmost element.

However, within a binary pattern, variables bound in a field can be used in other fields following it, left-to-right. Therefore, the following works (using a leading 32-bit size field in the binary):

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>>


回答2:

I've run into this before. There is some weirdness between what is happening inside binary syntax and what happens during unification (matching). I suspect that it is just that binary syntax and matching occur at different times in the VM somewhere (we don't know which Length is failing to get assigned -- maybe binary matching is always first in evaluation, so Length is still meaningless). I was once going to dig in and find out, but then I realized that I never really needed to solve this problem -- which might be why it was never "solved".

Fortunately, this won't stop you with whatever you are doing.

Unfortunately, we can't really help further unless you explain the context in which you think this kind of a match is a good idea (you are having an X-Y problem).

In binary parsing you can always force the situation to be one of the following:

  • Have a fixed-sized header at the beginning of the binary message that tells you the next size element you need (and from there that can continue as a chain of associations endlessly)
  • Inspect the binary once on entry to determine the size you are looking for, pull that one value, and then begin the real parsing task
  • Have a set of fields, all of predetermined sizes that conform to some a binary schema standard
  • Convert the binary to a list and iterate through it with any arbitrary amount of look-ahead and backtracking you might need

Quick Solution

Without knowing anything else about your general problem, a typical solution would look like:

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

But I smell something funky here.

Having things like this in your code is almost always a sign that a more fundamental aspect of the code structure is not in agreement with the data that you are handling.

But deadlines.

Erlang is all about practical code that satisfies your goals in the real world. With that in mind, I suggest that you do something like the above for now, and then return to this problem domain and rethink it. Then refactor it. This will gain you three benefits:

  1. Something will work right away.
  2. You will later learn something fundamental about parsing in general.
  3. Your code will almost certainly run faster if it fits your data better.

Example

Here is an example in the 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>>

Note that this version is a little safer, in that we avoid a crash in the case that Length is longer than the binary. This is yet another good reason why maybe we can't do that match in the function head.



回答3:

Try with below code:

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


标签: erlang