Variable is unbound when used to pattern match as

2019-08-02 07:19发布

I want to pattern match a map on a key when the second argument is a map with name's value being the same as the key in the first argument's map.

Instead I get this:

8> c(room).
room.erl:23: variable 'PlayerName' is unbound
error

status({_Players, _Tables, ChallengerName}, #{name := ChallengerName}) ->
  #{status => challenging};
status({#{PlayerName := PlayerStatus}, _, _}, #{name := PlayerName}) ->
  PlayerStatus;
status(_, _) ->
  #{status => null}.

Oddly, the first function declaration works fine where I pattern match an element in a tuple to a value in a map.

I found Pattern matching key in erlang maps - is there no way to pattern match on a key? What's the most efficient workaround?

2条回答
一纸荒年 Trace。
2楼-- · 2019-08-02 07:45

I've hoped just changing the parameter order would work, but apparently not. So you need to pattern-match inside the body, after Name is bound:

status(Triple, #{name := Name}) ->
  case Triple of
    {_Players, _Tables, Name} -> #{status => challenging};
    {#{Name := PlayerStatus}, _, _} -> PlayerStatus;
    _ -> #{status => null}
  end.
查看更多
啃猪蹄的小仙女
3楼-- · 2019-08-02 07:51

This is not an answer but rather a complement to Alexey answer.

As he does, I was thinking that changing the parameters order should be enough to solve your issue. I had in mind an execution model where the variables are bound during the pattern matching of each function head, from left to right. So if the PlayerName were bound in the first parameter match, it could be reused in the next variable as a map key to retrieve the PlayerStatus.

It does not work like this. In fact, using a variable twice in the function head like in eq2(X,X) -> ... is almost equivalent to add a guard condition like in eq1(Y,X) when Y =:= X -> ... To illustrate this I wrote a minimal module with the version of code, compiled it and disassembled and commented the result. You will see that apart of an inversion of the parameter order for the comparison, the 2 codes are identical.

-module(bar).

-compile([export_all]).

eq1(Y, X) when Y =:= X -> Y;
eq1(_, _) -> false.

eq2(X, X) -> true;
eq2(_, _) -> false.

% result of "rp(beam_disasm:file(bar))." command in the shell
%
% {beam_file,bar,
%            [{eq1,2,2},{eq2,2,5},{module_info,0,8},{module_info,1,10}],
%            [{vsn,[196965018902586160349592469625435725005]}],
%            [{options,[]},
%             {version,"7.0.3"},
%             {source,"c:/git/fourretout/src/bar.erl"}],
%            [{function,eq1,2,2,
%                       [{label,1},
%                        {line,1},
%                        {func_info,{atom,bar},{atom,eq1},2},
%                                         ^        ^      ^
%                                         |        |      |_ arity
%                                         |        |________ fuction name
%                                         |_________________ module name
%                        {label,2},
%                          ^
%                          |________________________________ first function head
%                        {test,is_eq_exact,{f,3},[{x,0},{x,1}]},
%                          ^        ^        ^      ^     ^
%                          |        |        |      |     |_ compare the second term of the function stack
%                          |        |        |      |_______ with the first term of the function stack
%                          |        |        |______________ branch to label 3 if test fails
%                          |        |_______________________ which test to perfom (here =:= exact equal)
%                          |________________________________ Instruction to perform: test
%                        return,
%                          ^
%                          |________________________________ Instruction to perform: return. the value returned is the first element of the
%                                                            function stack, in this case it is the first function parameter
%                        {label,3},
%                          ^
%                          |________________________________ second function head
%                        {move,{atom,false},{x,0}},
%                          ^        ^        ^
%                          |        |        |______________ where to move the value: the first element of the function stack
%                          |        |_______________________ what to move: the atom false
%                          |________________________________ Instruction to perform: move a value to some place
%                        return]},
%                          ^    ^
%                          |    |___________________________ last statement of the function
%                          |________________________________ Instruction to perform: return. the value returned is the first element of the
%                                                            function stack, in this case it is the atom false
%             {function,eq2,2,5,
%                       [{line,2},
%                        {label,4},
%                        {func_info,{atom,bar},{atom,eq2},2},
%                        {label,5},
%                        {test,is_eq_exact,{f,6},[{x,1},{x,0}]},
%                        {move,{atom,true},{x,0}},
%                        return,
%                        {label,6},
%                        {move,{atom,false},{x,0}},
%                        return]},
%             {function,module_info,0,8,
%                       [{line,0},
%                        {label,7},
%                        {func_info,{atom,bar},{atom,module_info},0},
%                        {label,8},
%                        {move,{atom,bar},{x,0}},
%                        {line,0},
%                        {call_ext_only,1,{extfunc,erlang,get_module_info,1}}]},
%             {function,module_info,1,10,
%                       [{line,0},
%                        {label,9},
%                        {func_info,{atom,bar},{atom,module_info},1},
%                        {label,10},
%                        {move,{x,0},{x,1}},
%                        {move,{atom,bar},{x,0}},
%                        {line,0},
%                        {call_ext_only,2,{extfunc,erlang,get_module_info,2}}]}]}

Even with more complex function head, you can verify that the compiler works only by making test (comparison, type check, size...) on the function parameters or some term extract from the function parmeters.

Edit

Curiously, it could be done relatively easily, I have compiled the following code to assembler ( using the command c(bar1,['S']) ):

-module(bar1).

-compile([export_all]).

status(#{name := ChallengerName},{_Players, _Tables, ChallengerName}) ->
  #{status => challenging};
status(#{name := PlayerName},{#{test := PlayerStatus}, _, _}) ->
  {PlayerName,PlayerStatus};
status(_, _) ->
  #{status => null}.

status1(Triple, #{name := Name}) ->
  case Triple of
    {_Players, _Tables, Name} -> #{status => challenging};
    {#{Name := PlayerStatus}, _, _} -> PlayerStatus;
    _ -> #{status => null}
  end.

then modified the assembler file to:

{module, bar1}.  %% version = 0

{exports, [{module_info,0},{module_info,1},{status,2},{status1,2}]}.

{attributes, []}.

{labels, 13}.


{function, status, 2, 2}.
  {label,1}.
    {line,[{location,"bar1.erl",5}]}.
    {func_info,{atom,bar1},{atom,status},2}.
  {label,2}.
    {test,is_map,{f,4},[{x,0}]}.
    {get_map_elements,{f,4},{x,0},{list,[{atom,name},{x,2}]}}.
    {test,is_tuple,{f,4},[{x,1}]}.
    {test,test_arity,{f,4},[{x,1},3]}.
    {get_tuple_element,{x,1},0,{x,3}}.
    {get_tuple_element,{x,1},2,{x,4}}.
    {test,is_eq_exact,{f,3},[{x,4},{x,2}]}.
    {move,{literal,#{status => challenging}},{x,0}}.
    return.
  {label,3}.
    {test,is_map,{f,4},[{x,3}]}.
    {get_map_elements,{f,4},{x,3},{list,[{x,2},{x,0}]}}.
// modif here: reuse the name that was stored in the third place {X,2}, assign the result to top of stack {x,0} and return if ok
    return.
  {label,4}.
    {move,{literal,#{status => null}},{x,0}}.
    return.


{function, status1, 2, 6}.
  {label,5}.
    {line,[{location,"bar1.erl",12}]}.
    {func_info,{atom,bar1},{atom,status1},2}.
  {label,6}.
    {test,is_map,{f,5},[{x,1}]}.
    {get_map_elements,{f,5},{x,1},{list,[{atom,name},{x,2}]}}.
    {test,is_tuple,{f,8},[{x,0}]}.
    {test,test_arity,{f,8},[{x,0},3]}.
    {get_tuple_element,{x,0},0,{x,1}}.
    {get_tuple_element,{x,0},2,{x,3}}.
    {test,is_eq_exact,{f,7},[{x,3},{x,2}]}.
    {move,{literal,#{status => challenging}},{x,0}}.
    return.
  {label,7}.
    {test,is_map,{f,8},[{x,1}]}.
    {get_map_elements,{f,8},{x,1},{list,[{x,2},{x,4}]}}.
    {move,{x,4},{x,0}}.
    return.
  {label,8}.
    {move,{literal,#{status => null}},{x,0}}.
    return.


{function, module_info, 0, 10}.
  {label,9}.
    {line,[]}.
    {func_info,{atom,bar1},{atom,module_info},0}.
  {label,10}.
    {move,{atom,bar1},{x,0}}.
    {line,[]}.
    {call_ext_only,1,{extfunc,erlang,get_module_info,1}}.


{function, module_info, 1, 12}.
  {label,11}.
    {line,[]}.
    {func_info,{atom,bar1},{atom,module_info},1}.
  {label,12}.
    {move,{x,0},{x,1}}.
    {move,{atom,bar1},{x,0}}.
    {line,[]}.
    {call_ext_only,2,{extfunc,erlang,get_module_info,2}}.

re-compile the module from the assembler ( c(bar1,[from_asm]). ) and then, in the shell it works as expected (in this code I have reverted the order of the parameters for the modified function status compare to status1 from Alexey, but it was not necessary), I wonder if this kind of pattern matching will be available in a future release of the compiler?

1> M1 = #{name => "Name"}.
#{name => "Name"}
2> T_chal = {none,none,"Name"}.
{none,none,"Name"}
3> T_stat = {#{"Name" => "Status"},none,none}.
{#{"Name" => "Status"},none,none}
4> T_null = {#{"Other" => "no show"},none,"Unknown"}.
{#{"Other" => "no show"},none,"Unknown"}
5> c(bar1,[from_asm]).
{ok,bar1}
6> bar1:status1(T_chal,M1).
#{status => challenging}
7> bar1:status1(T_stat,M1).
"Status"
8> bar1:status1(T_null,M1).
#{status => null}
9> bar1:status(M1,T_chal).
#{status => challenging}
10> bar1:status(M1,T_stat).
"Status"
11> bar1:status(M1,T_null).
#{status => null}
12>
查看更多
登录 后发表回答