Matching fun M:F/A

classic Classic list List threaded Threaded
11 messages Options
Reply | Threaded
Open this post in threaded view
|

Matching fun M:F/A

Pierre Fenoll-2
Hi,

Since a few releases, the value fun M:F/A (provided M, F & A are bound) is a literal. It can be read with file:consult/1 as well as erlang:binary_to_term/1.

Running OTP 22, funs can be compared:

eq(F) ->
    %% Compiles & works as expected.
    F == fun lists:keysearch/3.

However MFAs cannot be matched:

%% syntax error before: 'fun'
fmatch(fun lists:keysearch/3) -> true;
fmatch(_) -> false.

cmatch(F) ->
    case F of
        %% illegal pattern
        fun lists:keysearch/3 -> true;
        %% illegal guard expression
        X when X == fun lists:keysearch/3 -> true;

        %% illegal pattern
        fun lists:_/3 -> inte;
        fun _:handle_cast/2 -> resting;
        _ -> false
    end.

Is this intended?

I believe it would be interesting to allow such patterns as well as for fun _:_/_.
This could help in optimization through specialization and probably would make for some new approaches.
Among all funs only fully qualified funs can be expressed this way so this behaviour could be a bit surprising to some but MFAs are already comparable today so I'd say we're already halfway there.

Thoughts?

PS: it seems one is no longer able to log into bugs.erlang.org with GitHub credentials as https://bugs.erlang.org/login.jsp?os_destination=%2Fdefault.jsp doesn't provide the option anymore. Is this normal?

Reply | Threaded
Open this post in threaded view
|

Re: Matching fun M:F/A

Vyacheslav Levytskyy-2

Hello,

you can match MFA like the following:

test(Pred) ->
    test1(Pred, fun lists:keysearch/3).

test1(F, F) ->
    match;

test1(_, _) ->
    nomatch.

Erlang shell:

3> tonic_test:test(fun lists:keysearch/3).
match
4> tonic_test:test(fun lists:member/2).
nomatch

It looks like function expression is not a pattern expression in the grammar (pat_expr), and erl_lint identifies it eventually as an illegal pattern.

Best regards,
Vyacheslav


On 31.12.2019 17:12, Pierre Fenoll wrote:
Hi,

Since a few releases, the value fun M:F/A (provided M, F & A are bound) is a literal. It can be read with file:consult/1 as well as erlang:binary_to_term/1.

Running OTP 22, funs can be compared:

eq(F) ->
    %% Compiles & works as expected.
    F == fun lists:keysearch/3.

However MFAs cannot be matched:

%% syntax error before: 'fun'
fmatch(fun lists:keysearch/3) -> true;
fmatch(_) -> false.

cmatch(F) ->
    case F of
        %% illegal pattern
        fun lists:keysearch/3 -> true;
        %% illegal guard expression
        X when X == fun lists:keysearch/3 -> true;

        %% illegal pattern
        fun lists:_/3 -> inte;
        fun _:handle_cast/2 -> resting;
        _ -> false
    end.

Is this intended?

I believe it would be interesting to allow such patterns as well as for fun _:_/_.
This could help in optimization through specialization and probably would make for some new approaches.
Among all funs only fully qualified funs can be expressed this way so this behaviour could be a bit surprising to some but MFAs are already comparable today so I'd say we're already halfway there.

Thoughts?

PS: it seems one is no longer able to log into bugs.erlang.org with GitHub credentials as https://bugs.erlang.org/login.jsp?os_destination=%2Fdefault.jsp doesn't provide the option anymore. Is this normal?

Reply | Threaded
Open this post in threaded view
|

Re: Matching fun M:F/A

Mikael Pettersson-5
In reply to this post by Pierre Fenoll-2
On Tue, Dec 31, 2019 at 5:12 PM Pierre Fenoll <[hidden email]> wrote:

>
> Hi,
>
> Since a few releases, the value fun M:F/A (provided M, F & A are bound) is a literal. It can be read with file:consult/1 as well as erlang:binary_to_term/1.
>
> Running OTP 22, funs can be compared:
>
> eq(F) ->
>     %% Compiles & works as expected.
>     F == fun lists:keysearch/3.
>
> However MFAs cannot be matched:
>
> %% syntax error before: 'fun'
> fmatch(fun lists:keysearch/3) -> true;
> fmatch(_) -> false.
>
> cmatch(F) ->
>     case F of
>         %% illegal pattern
>         fun lists:keysearch/3 -> true;
>         %% illegal guard expression
>         X when X == fun lists:keysearch/3 -> true;
>
>         %% illegal pattern
>         fun lists:_/3 -> inte;
>         fun _:handle_cast/2 -> resting;
>         _ -> false
>     end.
>
> Is this intended?
>
> I believe it would be interesting to allow such patterns as well as for fun _:_/_.
> This could help in optimization through specialization and probably would make for some new approaches.
> Among all funs only fully qualified funs can be expressed this way so this behaviour could be a bit surprising to some but MFAs are already comparable today so I'd say we're already halfway there.

I believe this is mostly a historical accident.  Erlang started out as
a first-order language, and functional values were a later addition
(made in several increments I believe).  Also, functional values are
mostly opaque while pattern-matching is entirely about determining the
shape of non-opaque types, so I'm not surprised that fun M:F/A like
patterns aren't supported.

Having said that, there's no inherent reason why fun M:F/A couldn't be
a pattern, but the effort required to implement it would be
substantial, and I question how much real-world value it would
provide.

A slightly |ower-cost alternative that would still be useful would be
to make fun_info/2 a guard BIF, but in that case it should return the
requested value directly and not wrapped as a {Key,Value} tuple.  The
old is_function(F, A) would then be an alias for is_function(F),
fun_info(F, arity) == A.
Reply | Threaded
Open this post in threaded view
|

Re: Matching fun M:F/A

Jesper Louis Andersen-2
In reply to this post by Pierre Fenoll-2
On Tue, Dec 31, 2019 at 5:12 PM Pierre Fenoll <[hidden email]> wrote:
Thoughts?


Admitting equality and by extension unification on function values is a dangerous game which you usually don't want to play. The reason is that equality often has to be tailored to the situation.

Stake #1 in the vampire: Function equality is undecidable[0]. We can't generally handle this:

F = fun(X) -> X + 3 end,
G = fun(X) -> X + 2 + 1 end,
F==G.

though an optimizing compiler might decide to turn G into the exact same byte code instructions as F.

Stake #2 in the vampire: reference equality is not very Erlang-like, but it gives you *some* equality over the function domain. We tend to test equality by value only.

Stake #3 in the vampire: sensible languages outright *reject* equality on certain types. And by extension, not all types can be compared in the usual sense.

Stake #4 in the vampire: you often want to equip several different types of equality to a type[1]

[0] Sketch of proof: reduce to a solution to the halting problem. Also see Rice's theorem.
[1] Classic Haskell98 has a couple of weaknesses here. If you implement Eq, then you cannot easily implement another kind of Eq for the same type unless you newtype-wrap it. It is also prevalent in the Ord class with orders.


--
J.
Reply | Threaded
Open this post in threaded view
|

Re: Matching fun M:F/A

Dániel Szoboszlay
On Thu, 2 Jan 2020 at 13:41, Jesper Louis Andersen <[hidden email]> wrote:
Admitting equality and by extension unification on function values is a dangerous game which you usually don't want to play. The reason is that equality often has to be tailored to the situation.


It may be a bit aside to the original topic, but I don't understand this argument. Erlang already defines equality of function values, even pattern matching works for them with the = operator. The proposal was not about (re)defining the semantics of these operations but making the language more consistent on where does it allow the use of them.

Also, while it's true that function equality is undecidable, functions are not the only data type where the language's built-in equality and pattern matching are not tailored to the situation. In fact, it's quite common that complex data structures allow expressing the same value in different, unequal forms. For example both {[2],[1]} and {[],[1,2]} describe the same queue value, yet they won't compare equal. I believe this is something programmers are expected to understand and be aware of, and therefore they shall also understand and be aware of the limitations of the built-in function equality.

Cheers,
Daniel

PS: I played a bit with function equality and pattern matching while writing my reply, and I personally found all but the last of these examples intuitive (by the way, they all work with = in place of =:= too):
1> F1 = fun lists:sort/1.
fun lists:sort/1
2> F2 = fun lists:sort/1.
fun lists:sort/1
3> F1 =:= F2.
true
4> F = fun (X) -> F1([x | X]) end.
#Fun<erl_eval.6.128620087>
5> F =:= F.
true
6> G = fun (X) -> F1([x | X]) end.
#Fun<erl_eval.6.128620087>
7> F =:= G.                      
true
8> H = fun (X) -> F2([x | X]) end.
#Fun<erl_eval.6.128620087>
9> F =:= H.                      
false


For anyone curious, F and H are not equal because we're in the shell, and funs created in the shell will essentially take their source code in a free variable, which for H is the following tuple:
{[{'F2',fun lists:sort/1}],
 {eval,#Fun<shell.21.103068397>},
 {value,#Fun<shell.5.103068397>},
 [{clause,1,
          [{var,1,'X'}],
          [],
          [{call,1,{var,1,'F2'},[{cons,1,{atom,1,x},{var,1,'X'}}]}]}]}

Now it's obvious that while variables F1 and F2 are bound to the same function value and would compare equal, shell funs using them would also contain their variable names, which are different.


Reply | Threaded
Open this post in threaded view
|

Re: Matching fun M:F/A

Pierre Fenoll-2
Admitting equality and by extension unification on function values is a dangerous game which you usually don't want to play. The reason is that equality often has to be tailored to the situation.

Ah so this is the question I aimed to sidestep. This is not about anonymous funs nor un-exported functions.
As Dániel expresses I think more clearly than I: 
> The proposal was not about (re)defining the semantics of these operations but making the language more consistent on where does it allow the use of them.

Now I understand the use for this "cleaning up of some dark corner of the Erlang language" might not be obvious yet
and it may bring more puzzling (to newcomers?) than expressiveness powers. I'm only asking:

Is there a story behind this maybe unfortunate language result?
How do you feel about being able to pattern match on fun M:F/A?

Thanks & happy new years
-- 
Pierre Fenoll



On Fri, 3 Jan 2020 at 00:16, Dániel Szoboszlay <[hidden email]> wrote:
On Thu, 2 Jan 2020 at 13:41, Jesper Louis Andersen <[hidden email]> wrote:
Admitting equality and by extension unification on function values is a dangerous game which you usually don't want to play. The reason is that equality often has to be tailored to the situation.


It may be a bit aside to the original topic, but I don't understand this argument. Erlang already defines equality of function values, even pattern matching works for them with the = operator. The proposal was not about (re)defining the semantics of these operations but making the language more consistent on where does it allow the use of them.

Also, while it's true that function equality is undecidable, functions are not the only data type where the language's built-in equality and pattern matching are not tailored to the situation. In fact, it's quite common that complex data structures allow expressing the same value in different, unequal forms. For example both {[2],[1]} and {[],[1,2]} describe the same queue value, yet they won't compare equal. I believe this is something programmers are expected to understand and be aware of, and therefore they shall also understand and be aware of the limitations of the built-in function equality.

Cheers,
Daniel

PS: I played a bit with function equality and pattern matching while writing my reply, and I personally found all but the last of these examples intuitive (by the way, they all work with = in place of =:= too):
1> F1 = fun lists:sort/1.
fun lists:sort/1
2> F2 = fun lists:sort/1.
fun lists:sort/1
3> F1 =:= F2.
true
4> F = fun (X) -> F1([x | X]) end.
#Fun<erl_eval.6.128620087>
5> F =:= F.
true
6> G = fun (X) -> F1([x | X]) end.
#Fun<erl_eval.6.128620087>
7> F =:= G.                      
true
8> H = fun (X) -> F2([x | X]) end.
#Fun<erl_eval.6.128620087>
9> F =:= H.                      
false


For anyone curious, F and H are not equal because we're in the shell, and funs created in the shell will essentially take their source code in a free variable, which for H is the following tuple:
{[{'F2',fun lists:sort/1}],
 {eval,#Fun<shell.21.103068397>},
 {value,#Fun<shell.5.103068397>},
 [{clause,1,
          [{var,1,'X'}],
          [],
          [{call,1,{var,1,'F2'},[{cons,1,{atom,1,x},{var,1,'X'}}]}]}]}

Now it's obvious that while variables F1 and F2 are bound to the same function value and would compare equal, shell funs using them would also contain their variable names, which are different.


Reply | Threaded
Open this post in threaded view
|

Re: Matching fun M:F/A

José Valim-2
> How do you feel about being able to pattern match on fun M:F/A?

I personally don't agree with the argument of being "inconsistent". The pattern matching syntax has always been a subset of the language and with specific semantics. For example: #{1 => 2} is a literal map but #{1 => 2} literally in a pattern will also match #{1 => 2, 3 => 4}. I also consider the fact "fun M:F/A" is a literal to be an implementation detail rather than part of the language specification.

Finally, I think pattern matching on fun M:F/A would lead to bad coupling (it couples to the function name). For example, imagine that I implement some function called "my_module:my_fun/1" which has some specific behaviour on "fun lists:keysearch/3". At first, a user of my API is calling it as expected:

my_module:my_fun(fun lists:keysearch/3, List).

And imagine elsewhere the user has to use the same function, but the indexes are zero-based, so they have to wrap it like this:

my_module:my_fun(fun(Key, N, AList) -> lists:keysearch(Key, N + 1, AList) end, List).

Those two functions will now behave in different ways, which would be extremely confusing. Sure, you can achieve this by doing the manual comparison with == (as with everything else) but I don't think we should be further encouraging this behaviour. I personally don't remember ever needing to compare function references.

--
José Valim

Reply | Threaded
Open this post in threaded view
|

Re: Matching fun M:F/A

Richard O'Keefe
In reply to this post by Pierre Fenoll-2
I'm trying to think of a non-*accidental* semantics for fun patterns.
fun _:_/n matches any function reference of arity n, which certainly
has something to do with how we can call it.
fun _:_/_ matches any function reference.
I'm struggling to imagine plausible use cases for this.
Do you have some examples?

On Wed, 1 Jan 2020 at 05:13, Pierre Fenoll <[hidden email]> wrote:

>
> Hi,
>
> Since a few releases, the value fun M:F/A (provided M, F & A are bound) is a literal. It can be read with file:consult/1 as well as erlang:binary_to_term/1.
>
> Running OTP 22, funs can be compared:
>
> eq(F) ->
>     %% Compiles & works as expected.
>     F == fun lists:keysearch/3.
>
> However MFAs cannot be matched:
>
> %% syntax error before: 'fun'
> fmatch(fun lists:keysearch/3) -> true;
> fmatch(_) -> false.
>
> cmatch(F) ->
>     case F of
>         %% illegal pattern
>         fun lists:keysearch/3 -> true;
>         %% illegal guard expression
>         X when X == fun lists:keysearch/3 -> true;
>
>         %% illegal pattern
>         fun lists:_/3 -> inte;
>         fun _:handle_cast/2 -> resting;
>         _ -> false
>     end.
>
> Is this intended?
>
> I believe it would be interesting to allow such patterns as well as for fun _:_/_.
> This could help in optimization through specialization and probably would make for some new approaches.
> Among all funs only fully qualified funs can be expressed this way so this behaviour could be a bit surprising to some but MFAs are already comparable today so I'd say we're already halfway there.
>
> Thoughts?
>
> PS: it seems one is no longer able to log into bugs.erlang.org with GitHub credentials as https://bugs.erlang.org/login.jsp?os_destination=%2Fdefault.jsp doesn't provide the option anymore. Is this normal?
>
Reply | Threaded
Open this post in threaded view
|

Re: Matching fun M:F/A

Fred Hebert-2


On Sun, Jan 5, 2020 at 8:13 AM Richard O'Keefe <[hidden email]> wrote:
I'm trying to think of a non-*accidental* semantics for fun patterns.
fun _:_/n matches any function reference of arity n, which certainly
has something to do with how we can call it.
fun _:_/_ matches any function reference.
I'm struggling to imagine plausible use cases for this.
Do you have some examples?


The latter pattern is not different from the is_function/1 guard, but the only place I can think of to call it (aside from serialization or hashing) would be when passing it to apply/2, which accepts a fun and a list of arguments.
Reply | Threaded
Open this post in threaded view
|

Re: Matching fun M:F/A

Fred Youhanaie-2
In reply to this post by Pierre Fenoll-2

On 04/01/2020 19:46, Pierre Fenoll wrote:
 > How do you feel about being able to pattern match on fun M:F/A?

The language debate aside, you can pattern match today using
erlang:fun_info_mfa/1 and tuple patterns. So, your fmatch/1 example can
be rewritten as

fmatch(F) when is_function(F) ->
     fmatch(fun_info_mfa(F));
fmatch({lists, search, 3}) ->
     true;
fmatch(_) ->
     false.

and the cmatch/1 example as:

cmatch(F) ->
     case fun_info_mfa(F) of
         ... use {M,F,A} triples for the patterns clauses ...


Cheers,
Fred

Reply | Threaded
Open this post in threaded view
|

Re: Matching fun M:F/A

Björn Gustavsson-4
In reply to this post by Pierre Fenoll-2
On Tue, Dec 31, 2019 at 5:12 PM Pierre Fenoll <[hidden email]> wrote:

> Is this intended?

Yes. There does not seem to be any compelling use cases, and the
implementation is non-trivial.

Also, while one minor inconsistency would be removed, another would be
added in that one could match external funs and not local funs.

/Björn
--
Björn Gustavsson, Erlang/OTP, Ericsson AB