New EEP draft: Pinning operator ^ in patterns

Raimo Niskanen raimo+erlang-questions@REDACTED
Wed Jan 20 15:42:37 CET 2021


I have vague feeling that this has been asked,
but since I can not find it:

How is nested fun()s handled?

foo(Y) ->
    F = fun (X) ->
	    Y = X + ^Y,
	    FF = fun (Z) ->
	             Z + ^Y
	         end,
	    FF(Y)
        end,
    F(Y).

Does the innermost Z + ^Y access the outermost Y from foo(Y), or the Y
bound in F/1 i.e Y = X + ^Y?

Is there a way to choose which of the outer Y:s to refer to from within FF/1?

Cheers
/ Raimo



On Thu, Dec 24, 2020 at 09:10:17PM +0100, Richard Carlsson wrote:
> The ^ operator allows you to annotate already-bound pattern variables as
> ^X, like in Elixir. This is less error prone when code is being refactored
> and moved around so that variables previously new in a pattern may become
> bound, or vice versa, and makes it easier for the reader to see the intent
> of the code.
> 
> See also https://github.com/erlang/otp/pull/2951
> 
> Ho ho ho,
> 
>         /Richard & the good folks at WhatsApp

>     Author: Richard carlsson <carlsson.richard(at)gmail(dot)com>
>     Status: Draft
>     Type: Standards Track
>     Created: 21-Dec-2020
>     Erlang-Version: 24
>     Post-History: 24-Dec-2020
> ****
> EEP XXX: Pinning operator ^ in patterns
> ----
> 
> 
> Abstract
> ========
> 
> This EEP proposes the addition of a new unary operator `^` for
> explicitly marking variables in patterns as being already bound.  This
> is known as "pinning" in Elixir - see [Elixir doc][the Elixir
> documentation].
> 
> For example:
> 
>     f(X, Y) ->
>         case X of
>             {a, Y} -> ok;
>             _ -> error
>         end.
> 
> could be written more explicitly:
> 
>     f(X, Y) ->
>         case X of
>             {a, ^Y} -> ok;
>             _ -> error
>         end.
> 
> In Elixir, this operator is strictly necessary for being able to refer
> to the value of a bound variable as part of a pattern, because
> variables in patterns are always regarded as being new shadowing
> instances (like in Erlang's fun clause heads), unless explicitly
> pinned.
> 
> In Erlang, they would be optional, but are still a good idea because
> they make programs more robust under edits and refactorings, and
> furthermore allow the use of pinned variables in fun clause heads and
> in comprehension generator patterns.
> 
> 
> Specification
> =============
> 
> A new unary operator `^` is added to Erlang, called the "pinning
> operator".  It may only be used in patterns, and only on variables.
> Its meaning is that the "pinned" variable is to be interpreted in the
> enclosing environment of the pattern, and its value used in its place
> for that position in the pattern.
> 
> In current Erlang, this behaviour is what happens automatically in
> ordinary matching constructs if the variable is already bound in the
> enclosing environment.  In the following example:
> 
>     f(X, Y) ->
>         case X of
>             {a, Y} -> {ok, Y};
>             _ -> error
>         end.
> 
> the use of `Y` in the pattern is regarded as a reference to the
> function parameter `Y`, instead of as introducing a new variable, and
> the `Y` in the clause body is then that same parameter.  Therefore,
> annotating the pattern variable as `^Y` in this case does not change
> the behaviour of the program, but makes the intent explicit:
> 
>     f(X, Y) ->
>         case X of
>             {a, ^Y} -> {ok, Y};
>             _ -> error
>         end.
> 
> For fun expressions and list comprehension generator patterns, the
> pinning operator makes the language more expressive.  Take the
> following Erlang code:
> 
>     f(X, Y) ->
>         F = fun ({a, Y}) -> {ok, Y};
>                 (_) -> error
>             end,
>         F(X).
> 
> Here, the occurrence of `Y` in the clause head of the fun `F` is a new
> variable instance, shadowing the `Y` parameter of `f(X, Y)`, and the
> fun clause will match any value in that position.  The `Y` in the
> clause body is the one bound in the clause head.  However, using the
> pinning operator, we can selectively match on variables bound in the
> outer scope:
> 
>     f(X, Y) ->
>         F = fun ({a, ^Y})  -> {ok, Y};
>                 (_) -> error
>             end,
>         F(X).
> 
> In this case, there is no new binding of `Y`, and the use of `Y` in
> the fun clause body refers to the function parameter.  But it is also
> possible to combine pinning and shadowing in the same pattern:
> 
>     f(X, Y) ->
>         F = fun ({a, ^Y, Y})  -> {ok, Y};
>                 (_) -> error
>             end,
>         F(X).
> 
> In this case, the pinned field refers to the value of the function
> function parameter, but there is also a new shadowing binding of `Y`
> to the third field of the tuple.  The use in the fun clause body now
> refers to the shadowing instance.
> 
> Generator patterns in list comprehensions or binary comprehensions
> follow the same rules as fun clause heads, so with pinning we can for
> example write the following code:
> 
>     f(X, Y) ->
>         [{b, Y} || {a, ^Y, Y} <- X].
> 
> where the `Y` in `{b, Y}` is the shadowing instance bound to the third
> element of the pattern tuple.
> 
> Finally, a new compiler flag `warn_unpinned_vars` is added, disabled
> by default, which if enabled makes the compiler emit warnings about
> all uses of already bound variables in patterns that are not
> explicitly annotated with the `^` operator.  This allows users to
> migrate their code module by module towards using explicit pinning in
> all their code.  If pinning becomes the norm in Erlang, this flag
> could be turned on by default, and eventually, the pinning operator
> could become strictly required for referring to already bound
> variables in patterns.
> 
> 
> Rationale
> =========
> 
> The explicit pinning of variables in patterns make programs more
> readable, because the intent of the code becomes clear.  When already
> bound variables are used in Erlang without any annotation, anyone
> reading a piece of code must first study it closely to understand
> which variables will be bound at the point of a pattern, before they
> can tell whether any pattern variable is a new binding or implies an
> equality assertion.  This is easy to miss even for experienced
> Erlangers, be it during code reviews or while trying to understand a
> piece of poorly commented code.
> 
> Perhaps more importantly, pinning also makes programs more robust
> under edits and refactorings.  Take our previous example, and add a
> print statement:
> 
>     f(X, Y) ->
>         io:format("checking: ~p", [Y]),
>         case X of
>             {a, Y} -> {ok, Y};
>             _ -> error
>         end.
> 
> Suppose someone renames the function parameter from `Y` to `Z` and
> updates the print statement but forgets to update the use in the case
> clause.  Without an explicit pinning annotation, the change would be
> quietly allowed, but the `Y` in the pattern would be interpreted as a
> new variable that will match any value, which will then be used in the
> body.  This changes the behaviour of the program.  If the use in the
> pattern had been annotated as `^Y`, the compiler would have generated
> an error "Y is unbound" and the mistake would have been caught.
> 
> When code is being modified to add a feature or fix a bug, a
> programmer might want to introduce a new variable for a temporary
> result.  In a long function body, this risks introducing a new bug.
> Consider the following:
> 
>     g(Stuff) ->
>        ...
>        Thing = case ... of
>                    {a, T} -> T;
>                    _ -> 0
>                end,
>        ...
>        {ok, [Thing|Stuff]}.
> 
> Here, `T` is a new variable, clearly intended as just a temporary and
> local variable for extracting the second element of the tuple.  But
> suppose that someone adds a binding of the name `T` further up in the
> function body, without noticing that the name is already in use:
> 
>     g(Stuff) ->
>        ...
>        T = q(Stuff) + 1,
>        io:format("~p", [p(T)]),
>        ...
>        Thing = case ... of
>                    {a, T} -> T;
>                    _ -> 0
>                end,
>        ...
>        {ok, [Thing|Stuff]}.
> 
> Now the first clause of the case switch will only match if the second
> element of the tuple has the exact same value as the previously
> defined `T`.  Again, the compiler quietly accepts this change, while
> if it had been instructed to warn about all non-annotated uses of
> already bound variables in patterns, this mistake would have been
> detected.
> 
> 
> Shadowing in Funs and Comprehensions
> ------------------------------------
> 
> In funs and comprehensions, pinning also lets us do things that
> otherwise requires additional temporary variables.  Consider the
> following code:
> 
>     f(X, Y) ->
>         F = fun ({a, Y}) -> {ok, Y};
>                 (_) -> error
>             end,
>         F(X).
> 
> Since the `Y` in the clause head of the fun is a new shadowing
> instance, the pattern will match any value in that position.  To match
> only the value passed as `Y` to `f`, a clause guard must be added, and
> a temporary variable be used to access the outer `Y`:
> 
>     f(X, Y) ->
>         OuterY = Y,
>         F = fun ({a, Y}) when Y =:= OuterY -> {ok, Y};
>                 (_) -> error
>             end,
>         F(X).
> 
> We could instead rename the inner use of `Y` to avoid shadowing, but
> the equality test must still be written as an explicit guard:
> 
>     f(X, Y) ->
>         F = fun ({a, Z}) when Z =:= Y -> {ok, Y};
>                 (_) -> error
>             end,
>         F(X).
> 
> With the help of the pinning operator, such things are no longer a
> concern, and we can simply write:
> 
>     f(X, Y) ->
>         F = fun ({a, ^Y}) -> {ok, Y};
>                 (_) -> error
>             end,
>         F(X).
> 
> Furthermore, in the odd case that the pattern would both need to
> access the surrounding definition of `Y` as well as introduce a new
> shadowing binding, this can be easily written using pinning:
> 
>     f(X, Y) ->
>         F = fun ({a, ^Y, Y})  -> {ok, Y};
>                 (_) -> error
>             end,
>         F(X).
> 
> but in current Erlang, two separate temporary variables would be
> required:
> 
>     f(X, Y) ->
>         OuterY = Y,
>         F = fun ({a, Temp, Y}) when Temp =:= OuterY -> {ok, Y};
>                 (_) -> error
>             end,
>         F(X).
> 
> As explained before, the same goes for patterns in generators of
> comprehensions.
> 
> 
> 
> Backwards Compatibility
> =======================
> 
> The addition of a new and previously unused operator `^` does not
> affect the meaning of existing code, and the compiler will not emit
> any new warnings or errors for existing code, unless explicitly
> enabled with `warn_unpinned_vars`.  This change is therefore fully
> backwards compatible.
> 
> 
> 
> Implementation
> ==============
> 
> The implementation can be found in [PR #2951][pr].
> 
> 
> 
> Copyright
> =========
> 
> This document has been placed in the public domain.
> 
> 
> [Elixir doc]: https://elixir-lang.org/getting-started/pattern-matching.html#the-pin-operator
>     "Elixir pattern matching - the pin operator"
> 
> [pr]: https://github.com/erlang/otp/pull/2951
>     "#2951: Add a new operator ^ for pinning of pattern variables"
> 
> 
> 
> [EmacsVar]: <> "Local Variables:"
> [EmacsVar]: <> "mode: indented-text"
> [EmacsVar]: <> "indent-tabs-mode: nil"
> [EmacsVar]: <> "sentence-end-double-space: t"
> [EmacsVar]: <> "fill-column: 70"
> [EmacsVar]: <> "coding: utf-8"
> [EmacsVar]: <> "End:"
> [VimVar]: <> " vim: set fileencoding=utf-8 expandtab shiftwidth=4 softtabstop=4: "


-- 

/ Raimo Niskanen, Erlang/OTP, Ericsson AB


More information about the erlang-questions mailing list