[erlang-questions] pattern matching with maps in function heads

zxq9 <>
Fri Oct 30 00:09:14 CET 2015


On 2015年10月29日 木曜日 17:33:06 Martin Koroudjiev wrote:
> Hello,
> 
> How can I do pattern matching with maps in function heads? I tried
> 
> -module(m1).
> 
> -export([main/0]).
> 
> main() ->
>     M = #{a => 1, b => 2, c => 3},
>     io:format("b: ~p~n", get_attr(b, M)),
>     io:format("d: ~p~n", get_attr(d, M)).
> 
> get_attr(Attr, #{Attr := Val} = Map) ->
>     Val;
> get_attr(_Attr, _M) ->
>     not_found.
> 
> but I get compile error: m1.erl:10: variable 'Attr' is unbound

The docs say "Matching of literals as keys are allowed in function heads", but nothing else about this, so it is safe to assume this is not supported -- and the error message confirms it. Technically I imagine this should be possible to implement.

That said, there isn't any reason to support this I can think of other than to implement a function like the one above, which would simply be a reimplementation of a feature already supported two different ways (map syntax and the maps module).

In practice, the calling function will have already pulled the value from the map, the programmer will know the desired key as a literal value, and/or the called function will pull the value from the map. In all cases extraction of the value will happen much more naturally on either side of the function call, not within the function head. It is a bit convoluted to expect it to work otherwise -- it fattens your function head and adds an argument to it for no reason.

Think of it this way: double-matches within a function head are used most often to *assert* that something is true, that a particular match should indicate that a particular clause should be the one executed. In the case above the only thing that can be asserted is whether or not a particular key is in the given map -- and that is something there are already functions for.

I think the current idiom is clearer for the case where a clause is selected based on whether a key exists in the course of processing:

foo(Bar, SomeMap) ->
    % ... stuff
    some_fun(maps:get(Bar, SomeMap, undefined)),
    % ... more stuff

some_fun("some literal value")  -> next_thing();
some_fun("a different literal") -> alt_next_thing();
some_fun(undefined)             -> default_oops_thing().

The above is such a simplified example, that I can't actually imagine writing that exact sort of code. But I *can* imagine writing something a bit more interesting, like:

foo(Bar, SomeMap) ->
    % ... stuff
    some_fun(Bar, maps:get(Bar, SomeMap, undefined)),
    % ... more stuff

some_fun(Bar, {notice, Message}) -> send_user(Bar, Message);
some_fun(Bar, {system, Message}) -> handle_sys(Bar, Message);
some_fun(Bar, undefined)         -> log_missing(Bar).

I like this more than:

foo(Bar, SomeMap) ->
    % stuff
    some_fun(Bar, SomeMap),
    % stuff

some_fun(Bar, #{Bar := {notice, Message}) -> ...;
some_fun(Bar, #{Bar := {system, Message}) -> ...;
some_fun(Bar, _) -> ...

This just feels too noisy.

As we've seen many times on the ML before, though, it can be difficult to extract concrete examples from isolated code examples like this. Maybe someone else can think of a case where it would be really useful to match variable keys in function heads, I just can't think of any case where I would find it clearer than the current way of doing things (also I don't really *want* there to be 10 different ways to achieve the same effect -- part of why I like Erlang is that it isn't full of C++-style syntactic hairballs; but that's me).


More information about the erlang-questions mailing list