New EEP draft: Pinning operator ^ in patterns

Raimo Niskanen raimo+erlang-questions@REDACTED
Wed Jan 20 13:46:47 CET 2021


On Wed, Jan 20, 2021 at 01:47:12AM +0100, Chris Rempel wrote:
> Hello,
> 
> > I agree about the beauty of Erlang's simplicity. For this particular
> > suggestion I, personally, feel that because Erlang "hides" if a variable
> > occurence is a binding or a matching in the knowledge if the variable has
> > occured before or not, it may be too simple because it is slightly ambigous.
> >
> > Therefore I think that although annotating matches would make the language
> > less simple it would improve clarity by making intention explicit.
> >
> > I can live without this, have so for the last 20 years, but I think Erlang
> > would be a slightly better language with pinning than it is without it.
> >
> > / Raimo Niskanen, Erlang/OTP, Ericsson AB
> 
> I find a different conclusion in this, with how I think when coding in Erlang.
> Knowing a variable always has the same value after first use, whether assigned
> or pattern match, and the value never can change, is such simple concept to
> work with.

The proposal does not change that property.

> 
> >From the EEP:
> 
> f (X, Y) ->
>   case X of
>     {a, Y} -> {ok ,Y};
>     _ -> error
>   end.
> 
> would no longer be valid (at some point in the distant future when pinning is
> mandatory), and would have to be written:
> 
> f (X, Y) ->
>   case X of
>     {a, ^Y} -> {ok ,Y};
>     _ -> error
>   end.
> 
> yet today we can already write:
> 
> f (X, Y) ->
>   case X of
>     {a, Y2} when Y2 =:= Y -> {ok ,Y2};
>     _ -> error
>   end.
> 
> So, for those who want this EEP in order to be more explicit, why do you not
> write your code in that third form and introduce an EEP requesting a compiler

I think that is too clumsy, just as it annoys me to have to do that when
defining fun()s:
    Y = value,
    F = fun (YY) when YY =:= Y -> Y end,

> flag that you can use to generate a warning in the first form above, so that
> you rewrite it in the third form. The second form flies in the face of how I,
> and apparently many others, think in Erlang. The pinning operator is
> extraneous, and in fact determintal to that way of thinking.

I have noticed the opinion that this breaks how they think about Erlang,
and above you mentioned the principle that the same variable has the same
value in all occurences.  I say that this proposal does not break that
principle.

So I have a hard time understanding exactly what way to think it is that the
pinning operator destroys.

    Y = foo(),
    case bar() of
	{ok, ^Y} -> {ok, Y}
    end

Y is the same variable and has the same value in all 3 occurences.
But I know for sure that the code did not crash at Y = foo(), because
Y can not have been previously bound.


> 
> The good thing about this EEP is that it addresses variable shadowing.  It has
> always bugged me that variable shadowing was a thing in Erlang.
> 
> f(X, Y) ->
>   F = fun
>     ({a, Y}) -> {ok, Y};
>     (_) -> error
>   end,
>   {F(X), Y}.
> 
> That generates the shadow variable warning currently, and `f({a, 1}, 2)`
> returns `{{ok, 1}, 2}` which is just wrong as it breaks (in my view) the rule
> that a variable value never changes.  And so pointlessly this must be
> rewritten like so:
> 
> f(X, Y) ->
>   F = fun
>     ({a, Y2}) when Y2 =:= Y -> {ok, Y2};
>     (_) -> error
>   end,
>   {F(X), Y}.
> 
> The pinning operator "fixes" this by allowing for:
> 
> f(X, Y) ->
>   F = fun
>     ({a, ^Y}) -> {ok, Y};
>     (_) -> error
>   end,
>   {F(X), Y}.
> 
> But in both cases, I have to explicitly say, hey Erlang, don't break your own
> rule that a variable value never changes. To me this EEP is fixing the problem
> in the wrong way.

I do not see how the pinning operator breaks the rule that a variable's
value never changes.

A variable's value never changes for all occurences of the variable in its
scope.  The variable's scope is all of the function after its first occurence.

When we get to fun()s we have a conflict of principles.  Should the
the same scoping rule apply to the fun() as to the surrounding function?

Erlang has solved this with a pragmatic compromise (as often enough):
variables bound in a fun() are local to the fun().  But variables from the
outer scope that are referred to in a fun() are from the outer scope.  So
if it has the same name it is the one in the outer scope.  Except in the
fun() argument list.  Here variables are in general new ones so they are
local to the fun(), even if a different variable with the same name exists
in the outer scope.

But a variable's value never changes within its scope.  Not today.
Not with this proposal.  The snag is; which variable do we mean?

> 
> So, if there is any proposal to put forth, in my view, it is to get rid of
> variable shadowing.

You are free to sketch on one; it does not have to be an EEP.

A problem is that today variables in a fun() head shadows variables from
the outer scope, and the compiler warns about it.  If the behaviour should
be changed into not shadowing variables the compiler could not warn about
using a variable from the outer scope because it is now assumed that it is
the correct semantics.  If there was an annotation of the variables that
are bound instead of matched the compiler could tell the difference.  Such
an annotation would very much look like variable rebinding and could easily
be extended to exactly that.

Another possibility would be to introduce some kind of let...end construct
to explicitly introduce variable scoping, which has been discussed before.

There might, of course, be other possibilities.
I am not a language expert.

> 
> Also, already in this thread it is now being mentioned to have variable
> rebinding support. If this EEP erodes such a core tenant (in my view) of
> Erlang such that it leads to variable rebinding, it should be rejected on that
> alone (in my view).

I also do not want to get variable rebinding.  But this EEP should be
evaluated on its own merits and on the possibilities it offers that we
might want.

> 
> But the part of the EEP that just completely nullifies it is:
> 
> > 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.
> 
> You now have two variables named the same thing with different values. It
> breaks fundamental way of thinking in Erlang.  There is zero value in allowing
> this. Use a different variable name.

I agree that anyone that writes code like that deserves a good telling
off...

I think the problem is in the variable shadowing, not in the pinning operator.
Today we get a compiler warning when a variable in a fun() head shadows one
in the outer scope.  But other than that it is legal.

We do not, however, get a warning when a variable is used in a fun() body
from the surrounding function since a fun() body has got the same scope as
the surrounding function, except for newly bound variables...  Accidentally
referring to a variable from outside the fun() is a problem.

With the pinning operator it becomes possible to explicitly refer to the
outer scope and thereby possible to warn for referring to the containing
function without being explicit, making the fun() more a real variable
scope.

> 
> The EEP Rationale section is likewise problematic, but copying it here and
> responding to each point is tiresome.

That was some handwaving.  You are stating that it is problematic without
explaining why.  Not very constructive.

> 
> So, I'd like to propose that any EEP added to https://github.com/erlang/eep/
> is done in a PR and left open for reviews/comments on the PR for a period of
> time. There is much in the EEP that I would comment on, line by line.

That is an interesting suggestion.  EEP 1 states that after an EEP has been
accepted (not approved) it should try to get community support, but it does
not say how.

The current situation where this EEP is discussed in eeps@REDACTED,
erlang-questions@REDACTED, and in the PR for the reference implementation
is not good.

Having discussions about the EEP before submission (and after(?)) in a pull
request on the eep repository on GitHub sounds like a good step, except for
the ones that do not have accounts on GitHub, and it would drive us further
into the GitHub (/Microsoft) hands, which may be politically sensitive.

> 
> There has been concern on the list that this discussion has not remained
> technical and on point to the value of the EEP, and I would suggest the fault
> lies in the tool being used to elicit feedback (e-mail). That concern would be
> addressed by allowing people to comment line by line on the EEP and discuss
> the specific points of the EEP in the PR itself. And we can avoid blame being
> thrown around about people having or not having read the EEP.

A very valid point.  I also think GitHub Pull Requests offer better
possibilities for discussion around details.

> 
> Regards,
> 
> Chris

-- 

/ Raimo Niskanen, Erlang/OTP, Ericsson AB


More information about the erlang-questions mailing list