Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

To Twigil or Not To Twigil #29

Closed
Ovid opened this issue Aug 12, 2021 · 94 comments
Closed

To Twigil or Not To Twigil #29

Ovid opened this issue Aug 12, 2021 · 94 comments

Comments

@Ovid
Copy link
Collaborator

Ovid commented Aug 12, 2021

In Corinna, you can write this:

class Point {
    has ($x, $y) :param;

    method move ($dx, $dy) {
        $x += $dx;
        $y += $dy;
    }
}

But instead of moving the point via differences, what if you wanted to just pass the new x and y values?

method move ($x,$y) {
    ...
}

You can't do that because the local variables $x and $y hide the instance variables. In Java, you could do this:

public void move (double x, double y) {
    this.x = x;
    this.y = y;

Corinna's semantics are sufficiently different from Java's that we don't have this option. What are our options?

  • Inner-scope lexical variables hide instance variables (this is pretty much what Perl does now)
  • Adopt (allow, require?) some kind of "twigil" for instance variables
  • Something else I haven't thought of?

The twigils might look like this:

class Point {
    has ($.x, $.y) :param;

    method move ($x, $y) {
        $.x = $x;
        $.y = $y;
    }
}

While I kind of like like that and it makes it immediately clear that this isn't just a normal local variable, nonetheless, introducing even more punctuation could increase Perl's reputation for line noise. Given that I don't see Java devs complaining too often about confusing local and instance variables, I'm not sure if we really have an issue, other than the fact that when our lexical variables hide instance variables, there's not much we can do to work around this other than providing a :writer:

class Point {
    has ($x, $y) :param :writer;

    method move ($x, $y) {
        $self->x($x);
        $self->y($y);
    }
}
@Ovid
Copy link
Collaborator Author

Ovid commented Aug 12, 2021

Vote! Should we have twigils or not? (note: this vote is not binding)


@duncand
Copy link

duncand commented Aug 12, 2021

You DEFINITELY should have separate namespaces for slots and parameters. It is normal and good that one should be able to use the same unqualified names for corresponding slots and parameters. However it is done is less important than THAT it is done.

@duncand
Copy link

duncand commented Aug 12, 2021

I believe the best solution aesthetically is something where if you have multiple objects of the current class a method of that class can access each of their members with the same verbosity. So if you have say a $self and an $other where the first is an implicit method argument and the second is an explicit argument then you can, say go $self::x and $other::x or something. If you had an actual twigil $.x or whatever for $self, what would you say for $other? So I support whatever gives balance between the 2 objects.

@xenu
Copy link

xenu commented Aug 12, 2021

introducing even more punctuation could increase Perl's reputation for line noise.

TBH, I don't think it's productive to worry about Perl's reputation. It's unlikely it will ever change, especially because of such a small change. Memes rarely go away.

@haarg
Copy link

haarg commented Aug 12, 2021

I believe the best solution aesthetically is something where if you have multiple objects of the current class a method of that class can access each of their members with the same verbosity. So if you have say a $self and an $other where the first is an implicit method argument and the second is an explicit argument then you can, say go $self::x and $other::x or something. If you had an actual twigil $.x or whatever for $self, what would you say for $other? So I support whatever gives balance between the 2 objects.

The problem with any syntax that includes the instance variable is that they are generally already valid syntax that isn't reasonable to overload. $self::x means the global variable $x in the package self. $self:x could be part of a ternary. $self.x is concatenating $self with the return from calling the sub x. Syntax like $:x or $.x on its own does not have this problem. I can't think of any syntax that actually works for this that isn't extremely hateful.

There hasn't been any discussion about providing access to members in other objects, even of the same class. All current and potential future plans have revolved around using methods for that, possibly with a trust or protection model.

@duncand
Copy link

duncand commented Aug 12, 2021

I believe the best solution aesthetically is something where if you have multiple objects of the current class a method of that class can access each of their members with the same verbosity. So if you have say a $self and an $other where the first is an implicit method argument and the second is an explicit argument then you can, say go $self::x and $other::x or something. If you had an actual twigil $.x or whatever for $self, what would you say for $other? So I support whatever gives balance between the 2 objects.

The problem with any syntax that includes the instance variable is that they are generally already valid syntax that isn't reasonable to overload. $self::x means the global variable $x in the package self. $self:x could be part of a ternary. $self.x is concatenating $self with the return from calling the sub x. Syntax like $:x or $.x on its own does not have this problem. I can't think of any syntax that actually works for this that isn't extremely hateful.

There hasn't been any discussion about providing access to members in other objects, even of the same class. All current and potential future plans have revolved around using methods for that, possibly with a trust or protection model.

To be clear, I'm not proposing any specific syntax like the :: or whatever, I only used that to illustrate the balance thing by using the same syntax for self/other.

I also want to be clear that I actually consider it a misfeature of common OO systems that there even is a conceptual difference between "self" and "other". I much prefer the conception in functional languages that you just have routines and they have parameters and you treat all the parameters the same.

Especially when you're talking about a routine that has multiple parameters of the same type and all the parameters are that type, such as comparison or addition or whatever, so you have foo($x,$y) where $x and $y are objects, you should be able to use the same syntax on both parameters to access their slots.

With classes the normal thing that everyone expects is that when a class declares a "private" slot or method, that all methods of that same class can access them for every object of that class on equal terms, and it makes no difference whether in a call $x->foo($y) whether it is the $x or $y object of that class.

Methods inside a class are NOT the same as methods of any other class.

Today is the first time I heard any notion of Corinna being designed to treat private slots differently as to whether class Foo can see them for $x and not $y. I hope that's not the case, it seems counter to any good design and seems unjustified.

@duncand
Copy link

duncand commented Aug 12, 2021

I suggest a solution could be some unused variant of ->. So normal -> is used for regular accessors generated by :reader or :writer etc like is normal for Perl. And then something like I don't know ->> or -!> pick something is how a class references its own private slots on any object whether $self or $other. So eg $self->>x = 42; and return $other->>y;. This is balanced, there is no twigil, and an appropriate choice doesn't conflict with anything that exists now.

@leonerd
Copy link
Collaborator

leonerd commented Aug 12, 2021

@duncand A difference between twigils and your proposed ->>, is that twigils will still interpolate nicely into strings.

@salva
Copy link

salva commented Aug 12, 2021

A couple of ideas:

  1. In the same way my and our exist, another keyword could be used to get an object instance variable into the scope. Maybe you can even reuse has for that:
class Foo {
  has $x;
  method ($x) {
    has $x = $x;
    # now $x refers to the instance variable
  }
}

I am not sure this bring-the-slot-into-scope behavior would be too user friendly but it removes the need for a new syntax and it is consistent when how scope is already managed.

  1. Twigils but surrounded by curly brackets:
class Foo {
  has $x;
  method ($x) {
    ${.x} = $x; 
  }
}

Using that in current perl syntax results in an error, so it doesn't conflict with any currently valid syntax.

Note also, that ${^foo} is also a special syntax, so there is some precedent for going that way.

Also, this {.foo} syntax can be abused in some other ways. For instance, for accessing other object slots without breaking emcapsulation: $other->{.foo} = 7 can be translated into something functionally similar to $CURRENT_CLASS->accessor($other, "foo") = 7; That would croak unless $other belongs to the class shared with $CURRENT_CLASS where slot foo was declared.

@Ovid
Copy link
Collaborator Author

Ovid commented Aug 12, 2021

@salva the has $x = $x is a very interesting idea, but I think it doesn't read well. However, there's been discussion of using slot or attr instead of has, though it's not been settled. However, if we go with slot, we get this:

class Point {
    slot ($x, $y) :param;

    method move ($x, $y) {
        slot $x = $x;
        slot $y = $y;
    }
}

That would neatly sidestep the issue, at the expense of overloading the meaning of the slot keyword.

@talexb
Copy link

talexb commented Aug 12, 2021

Leaving aside the twigil issue, why not have a move method that behaves in a relative manner, and a move_to method that does an absolute move?

@Ovid
Copy link
Collaborator Author

Ovid commented Aug 12, 2021

@talexb Those are both reasonable, but I needed to have an example which showed the inadvertent data hiding.

@oodler577
Copy link

https://docs.raku.org/language/variables is useful for 2 reasons:

  1. it clearly defines what a twigil is
  2. it shows that you're heading towards perl 6/raku, which for your intents I think is a huge mistake

If you want this to be successful (and I want to see it successful myself), it needs to tend towards Perl5. This is just what the last 20 years has shown us. Otherwise, you're just risking the reimplementation (or vulnerable to the accusation) of Perl 6/Raku.

@garu
Copy link

garu commented Aug 12, 2021

Leaving aside the twigil issue, why not have a move method that behaves in a relative manner, and a move_to method that does an absolute move?

@talexb I believe it's not about solving the presented code. If it were, one could simply use a different name and avoid the lexical trap altogether:

class Point {
    has ($x, $y) :param;

    method move ($new_x, $new_y) {
        $x = $new_x;
        $y = $new_y;
    }
}

I think @Ovid is just trying to expose the caveat that "local variables named like instance variables will not work". Even more so, they are an issue that will bite people, specially newcomers, because the '"my" variable $x masks earlier declaration in same scope' warning only happens when you actually declare them in the same scope, not when we declare a variable with the same name on an inner scope:

$ perl -WE 'my $x; my $x;'
"my" variable $x masks earlier declaration in same scope at -e line 1.

$ perl -WE 'my $x; { my $x; }'
(no warnings)

Because this is already an issue with Perl, I would NOT add twigils or any other form of "magic" workaround unless it's something pervasive throughout the language (which may never happen), or at the very least under regular signatures.

perl -Mfeature=signatures -WE 'my $x = 1; sub x ($x) { $x = $x } x(2); say $x'
The signatures feature is experimental at -e line 1.
1

So, until it is fixed in Perl (fsvo "fixed"), I wouldn't try to change this behavior in Corinna at all. I'd add it to the "CAVEATS" section of the docs and move on.

Which is to say I agree with @Ovid's final comment that "(...)'m not sure if we really have an issue, other than the fact that when our lexical variables hide instance variables, there's not much we can do to work around this other than providing a :writer:" (or using another name for either variable, or making the instance variables private and providing getters, or ...)

@oodler577
Copy link

@Grinnz ~ as an attempt to address your "confused" emoticon, I'd like to just simply put it this way. If Cor can't be discussed or implemented without sticking to familiar Perl 5 concepts, then this is a red flag and might indicate the need to seek a return to the ground state; that being things and stuff that will be familiar to perl 5 programmers - which Cor needs to keep as a potential set of users. Deviating from that is where I've seen all efforts fail - Perl 6 just being the most epic example - in terms of a natural progression of Perl 5 ( don't think this can be disputed ).

@vrurg
Copy link

vrurg commented Aug 12, 2021

@Ovid @salva By trying to avoid linenoise with twigils, you fall into another big problem: extra code for simple ops. Moreover, in the following example it's really weird to have lexical parameters being instantly overwritten with slots...

class Point {
    slot ($x, $y) :param;

    method move ($x, $y) {
        slot $x = $x;
        slot $y = $y;
    }
}

Instead you could consider doing this:

class Point {
    slot ($x, $y) :param;

    method move (slot $x, slot $y) {
    }
}

Or, depending on what is considered corinnish way of doing things:

class Point {
    slot ($x, $y) :param;

    method move ($x :slot, $y :slot) {
    }
}

The original idea about "lexical slots" could still be used though for when slots might be needed deeper inside the code:

class Point {
    slot ($x, $y) :param;

    method move ($x, $y) {
        if ($self->some_condition($x, $y)) {
            slot $x = $x;
            slot $y = $y;
            ...
        }
        else {
             die "The coords are bad, really bad!";
        }
    }
}

UPD Oh, and BTW: I'm pro-twigil anyway. The idea of attributes/slots looking same way as lexicals never looked good to me from code readability point of view. It's ok for small examples, but in big code with many slots it would require from one to remember all of them. And it's for one class alone. What about many classes in a project?

@haarg
Copy link

haarg commented Aug 12, 2021

@Grinnz ~ as an attempt to address your "confused" emoticon, I'd like to just simply put it this way. If Cor can't be discussed or implemented without sticking to familiar Perl 5 concepts, then this is a red flag and might indicate the need to seek a return to the ground state; that being things and stuff that will be familiar to perl 5 programmers - which Cor needs to keep as a potential set of users. Deviating from that is where I've seen all efforts fail - Perl 6 just being the most epic example - in terms of a natural progression of Perl 5 ( don't think this can be disputed ).

None of this has anything to do with Raku/Perl 6. Just because a term has been used by Raku does not mean it owns the term.

@oodler577
Copy link

@haarg you're grossly missing my point, but I digress

@TBSliver
Copy link

TBSliver commented Aug 12, 2021

Why not something like the following to access slots:

class Point {
    slot ($x, $y) :param;

    method move ($x, $y) {
        if ($self->some_condition($x, $y)) {
            $self{ x } = $x;
            $self{ y } = $y;
            ...
        }
        else {
             die "The coords are bad, really bad!";
        }
    }
}

gives a differentiation between methods accessed using -> and variables accessed using {...}, and kind of makes sense given existing perl ideas, basically making %self be the backend magic for accessing slots. Coming from a newbie perspective as well, it'l just look like $self is how you get to the classes items in general, making it easier to teach basic concepts of hash vs hashref later (assuming someone comes into perl and dives straight for Cor based code anyway)

@TBSliver
Copy link

TBSliver commented Aug 12, 2021

Minor aside for the example I just gave, it makes the following possible:

class Point {
  slot ($x, $y) :param;

  method move ($x, $y) {
    $self{ x } = $x;
    $self{ y } = $y;
  }

  # override the get for x for some reason
  method x () {
    return $self{ x } / 10;
  }
}

As I assume you'd use a Point class as such:

my $point = new Point;
$point->move(5,5);
say $point->x; # 0.5

Edit: Obviously I don't know if this will cause issues with any other parts of Cor such as overrides or accessors or setters, someone with more experience in the spec should see where those issues are though 😅

@davidnicol
Copy link

I don't think extended sigils are needed, because a naming custom -- such as is currently done with leading underscores in a lot of packages -- would work just fine. Why not follow that convention and always write

has ($_x, $_y) :param;

very simple very easy no problem any more.

@aaronpriven
Copy link

I'm not seeing that the override-outer-scopes thing is really a widespread problem.

In those rare cases where it is a problem, using has($_x) :name(x), or alternatively method ( :x($new_x), :y($new_y) ) , would avoid it.

@garu
Copy link

garu commented Aug 12, 2021

I agree with @davidnicol, specially since :param already removes leading underscores.

That said, this is yet another workaround. If the developer forgets about this and uses the same name, the "masks earlier declaration" warning will not show up and a hard to detect(?) bug will happen. But (again), since this already happens with signatures and pretty much any other inner block in perl, I think it should be added to a don't-do-this caveat section and that's it. Unless it's something worth changing everywhere.

@Grinnz
Copy link

Grinnz commented Aug 12, 2021

I don't think extended sigils are needed, because a naming custom -- such as is currently done with leading underscores in a lot of packages -- would work just fine. Why not follow that convention and always write

has ($_x, $_y) :param;

very simple very easy no problem any more.

Since it's a core feature we're designing right now, it seems like a mistake to design it to require a special workaround when we can just, not do that.

@djerius
Copy link

djerius commented Aug 12, 2021

Could the parser simply throw an error if a lexical variable has the same name as a slot? Maybe not Perlish (taking away a footgun) but it resolves the problem without new syntax.

@yiyian-Lee
Copy link

This is same performance of signatures and old my $x = shift style, maybe new hands can learn this automatically.

If imaging method works like:

sub move {
my $x = $self->{x};
my $y = $self->{y};
...
}

Throw a error looks like naturally choice.

@duncand
Copy link

duncand commented Aug 12, 2021

I don't think extended sigils are needed, because a naming custom -- such as is currently done with leading underscores in a lot of packages -- would work just fine. Why not follow that convention and always write

has ($_x, $_y) :param;

very simple very easy no problem any more.

I consider that a very dirty kludge. The only clean solution is that there are separate namespaces for slots from other things so people can use the same nice names for both without conflict.

@haarg
Copy link

haarg commented Aug 12, 2021

I agree with @davidnicol, specially since :param already removes leading underscores.

Object::Pad does this, because Object::Pad is a playground to experiment with these ideas. A core implementation will definitely not have any special handling for slots beginning with underscores.

@leonerd
Copy link
Collaborator

leonerd commented Aug 12, 2021

Object::Pad does this, because Object::Pad is a playground to experiment with these ideas. A core implementation will definitely not have any special handling for slots beginning with underscores.

Infact moreover, I put the underscore handling in precisely because of a lack of ability to do twigils there. If we had twigils then that wouldn't be required, as things like

has $:name :reader;

would already create a reader method named name, and not :name.

@leonerd
Copy link
Collaborator

leonerd commented Aug 12, 2021

This doc section may also be relevant

https://metacpan.org/pod/Object::Pad#Slot-Names

@nrdvana
Copy link

nrdvana commented Aug 14, 2021

2. it shows that you're heading towards perl 6/raku, which for your intents I think is a huge mistake

If you want this to be successful (and I want to see it successful myself), it needs to tend towards Perl5. This is just what the last 20 years has shown us. Otherwise, you're just risking the reimplementation (or vulnerable to the accusation) of Perl 6/Raku.

I would disagree with this... It's hard to speak for the general public, but I think most people were objecting to the Perl 6 treatment of Perl 5 code as a second-class citizen and the decreased performance all around and different threading model, rather than the fancy new syntax that Perl 6 offered.

@tom-binary
Copy link

One last comment from me, since I think my signal-to-noise ratio is dropping:

  • why the special-case for instance slots, as opposed to our, or sub parameters, or refaliases, or lexicals imported from the parent scope?

Instance slot lifecycle is unique to these other cases. It's not lexical or attached to a package.

Technically it is lexical, as in "only valid within the area of code in which it was declared" - you can't access the slot from outside the class scope, e.g. another file or after the class block. That aside, instance slot lifecycle isn't really unique, though. Those were just a few examples - take state, for example: if lifecycle was the relevant factor, then why not apply a different twigil there? Or closures, even: if lifecycle was the gripping hand for these decisions then I'd expect to see a push for a different sigil for "this variable can be captured in callbacks" vs. "this must only be used in its own scope"...

My concern is that there seems to be a general "instance slots must be easily distinguished from everything else" push from proponents, but I haven't really seen any convincing arguments as to why that's the case.

  • your example is using $_ twigil syntax that'd be just as easy for synhi as the $: proposal sweat_smile

Incorrect; $_ is currently a perfectly valid beginning of a variable name, and is thus not a twigil nor distinguishable from an ordinary variable by static analysis.

I see your "incorrect" and raise you one "irrelevant" in return 😸

So as an example, we already have upper-case or prefix-case for our variables - as a convention, and not one universally applied, but it's specifically called out in perlstyle among others.

I like the way use Log::Any qw($log) gives me a simple lowercase instance without enforcing "IT IS AN IMPORTED PACKAGE VARIABLE THEREFORE MUST BE UPPERCASE" rules. That also allows one simple change to switch from use Log::Any qw($log) to has $log, with no changes to any calling code: nothing has to care! The lifecycle is not a concern for that usage, and rewriting everything to use twigils would be a matter of some disappointment to me.

We have other conventions, too - such as the underscore for private methods: synhi is free to apply additional rules on these based on those conventions, despite them not being absolute rules.

If someone has a different convention for an underscore prefix, that's fine - but if synhi has a rule for displaying those differently, then no one has to care what the conventions are, just that we wanted some visual distinction, so we have the ability to apply that visual distinction, without necessarily enforcing it.

So yes, I stand by my original comment - if you want a twigil convention, there's a clear path: use $_ and configure synhi for that. All achievable and straightforward with what we have right now, which has the advantage of offering immediate opportunities for experimentation and building some experience on which to argue for/against the various approaches.

(as to @nrdvana 's comment: you can count me as one of the people who emphatically does object (hah) specifically to the "fancy new syntax" and general extra complexity of Raku, performance can be improved and threads are nice - I'm sure its proponents enjoy the language greatly, but from a teaching aspect it seems like someone took "line noise" as a challenge, and tried to upgrade to "square noise" instead 😟 )

@abraxxa
Copy link

abraxxa commented Aug 14, 2021

Do we want to support array and hash attributes (sorry, I hate the term ‚slot‘)?
In that case I‘d prefer the use of the existing twigils for those in addition to scalars.

@duncand
Copy link

duncand commented Aug 14, 2021

  • why the special-case for instance slots, as opposed to our, or sub parameters, or refaliases, or lexicals imported from the parent scope?

Instance slot lifecycle is unique to these other cases. It's not lexical or attached to a package.

Technically it is lexical, as in "only valid within the area of code in which it was declared" - you can't access the slot from outside the class scope, e.g. another file or after the class block. That aside, instance slot lifecycle isn't really unique, though. Those were just a few examples - take state, for example: if lifecycle was the relevant factor, then why not apply a different twigil there? Or closures, even: if lifecycle was the gripping hand for these decisions then I'd expect to see a push for a different sigil for "this variable can be captured in callbacks" vs. "this must only be used in its own scope"...

My concern is that there seems to be a general "instance slots must be easily distinguished from everything else" push from proponents, but I haven't really seen any convincing arguments as to why that's the case.

In context of a limited feature where we're just concerned about access to $self slots and not $other slots, ...

I now feel that using a keyword slot similar to keywords like my/state/etc as a disambiguator is a perfectly acceptable solution.

So the normal syntax to access a slot is just $foo the same as the syntax for any normal lexical or parameter, and the slots are considered to be in the lexical scope of the class/package same as a my $foo at the package level, and therefore a parameter or lexical declared inside a routine would hide that.

In practice probably it would be relatively rare (at most half of all methods) to have parameters with the same names as slots but when this happens one can say slot $foo or (slot $foo) to disambiguate and say one is referring to the slot, and the scope of this slot keyword as used within a routine is the $foo term immediately after it.

Its not quite as terse as a twigil when used, but in the cases where there isn't shadowing the regular $foo is terser still, and the keyword is more self documenting and more self-consistent with the rest of Perl.

If we want to introduce twigils to Perl its probably best to do so more comprehensively than just for a single use case as now.

@Grinnz
Copy link

Grinnz commented Aug 14, 2021

I now feel that using a keyword slot similar to keywords like my/state/etc as a disambiguator is a perfectly acceptable solution.

So the normal syntax to access a slot is just $foo the same as the syntax for any normal lexical or parameter, and the slots are considered to be in the lexical scope of the class/package same as a my $foo at the package level, and therefore a parameter or lexical declared inside a routine would hide that.

In practice probably it would be relatively rare (at most half of all methods) to have parameters with the same names as slots but when this happens one can say slot $foo or (slot $foo) to disambiguate and say one is referring to the slot, and the scope of this slot keyword as used within a routine is the $foo term immediately after it.

Its not quite as terse as a twigil when used, but in the cases where there isn't shadowing the regular $foo is terser still, and the keyword is more self documenting and more self-consistent with the rest of Perl.

If we want to introduce twigils to Perl its probably best to do so more comprehensively than just for a single use case as now.

"At most half of all methods" is relatively rare? I think there's a scale problem in this analysis.

I'm not entirely disagreeing, but the primary difference in use case is that slot variables will always be intermingled with lexical variables in every usage of them, whereas package variables are rarely used at the same time as lexicals.

@duncand
Copy link

duncand commented Aug 14, 2021

"At most half of all methods" is relatively rare? I think there's a scale problem in this analysis.

I'm not entirely disagreeing, but the primary difference in use case is that slot variables will always be intermingled with lexical variables in every usage of them, whereas package variables are rarely used at the same time as lexicals.

My point is that sometimes it is better design to give parameters the exact same names as slots and be able to access both, and sometimes it is better design for the parameters to have different names. Having shadowing and the slightly more verbose slot $foo disambiguation may create a small amount of friction encouraging the use of different names, but one can still use the same names where it is better design. And things work without breaking the uniformity that Perl has now by introducing twigils for just this specific case.

@Ovid
Copy link
Collaborator Author

Ovid commented Aug 15, 2021

Here's where I think we are.

Right now, one of the biggest problems we're having with Corinna is people saying "yes" or "no" without giving concrete explanations or examples of their reasoning. Fortunately in this thread, many people have been giving concrete examples and this has helped a lot.

For example, here's a slot with a twigil (twigil not yet decided):

has $:x;

method inc ($x) {
    $:x += $x;
    ...
}

Here are some of the arguments for and against them.

Pros:

  • You can't accidentally shadow class/instance data. (technical)
  • Great Huffman encoding for "this is not a regular variable" (social)
  • Easy to syntax highlight (technical)

Cons:

  • No direct parser support for twigils (technical)
  • Is somewhat controversial (social)
  • May contribute to "line-noise" complaints (social)

I can't say that I'm "for" twigils, but so far, that's two strong technical and one strong social argument for them. I see a technical argument against them (I don't know how strong it is) and two rather weak social arguments against them.

At the beginning of the discussion, I was leaning away from twigils. I kind of liked them, but I was swayed by the social arguments. Laying out the pros and cons clearly seems to show a strong benefit to using twigils.

So if you have a strong argument for or against them, if you can clearly describe it, give an example, and explain if it's a technical argument or not, I'd love to hear it (I'm quite serious about this because I'm happy to put a gun to the head of twigils, but I can't see any reason to do so at this point.)

For the following discussion, I assume that a strong technical argument beats a strong social argument.

Other solutions proposed are (not an exhaustive list) ...


Fake Slot Access

has $x;

method inc($x) {
    $self{x} += $x;
}

Pros:

  • Very clear as to what's going on (social)
  • Should feel comfortable to Perl devs (social)
  • Accessing $self{x} from a common method is a compile-time syntax error (technical)

Cons:

  • People are going to write $self->{x} and then moan when it breaks (or worse, silently fails)
  • Needs parser work to implement (technical, I don't know how hard this is)
  • Overloads the meaning of existing syntax (technical)
  • It's still shadowing variables (%self), though fewer of them (technical)

I find this proposal interesting. I do wonder about this case:

common $x;

method foo(x) {
    $self{x}
    # or
    $class{x}
}

In the above, it would always make it crystal clear that $class{x} is referring to class data. I don't know enough about the technical issues, other than to say that listing the pros and cons has helped me see that this is more interesting than I thought at first. However, it's pretending that $self and $class are hashes and that seems a strong knock against it.

However, there's not been enough discussion of this case for me to have a strong opinion.

Update: overloading the meaning of existing syntax and shadowing make this a show-stopper for me.


Ignore the issue since shadowing is already known

has $:x;

method inc ($x) {
    # oops
}

method inc2 ($dx) {
    $x += $dx;
}

Pros:

  • Very easy to implement (technical)

Cons:

  • Harder to maintain (social)
  • Silent bugs can be introduced (social)
  • Why should Perl be one of the few OO languages which has to suffer from this? (social)
  • In large classes, it's often confusing trying to figure out where a variable comes from (social)

So we have a strong technical argument for and, in my opinion, two strong and two weak social arguments against.

Conclusion: we cannot ignore this issue.


Reuse the slot/has keyword

slot $x;

method inc($x) {
    slot $x += $x;
}

Pros:

  • Easy to implement (technical)
  • Easy to see what's happening (social)

Cons:

  • Still allows accidental shadowing (technical)
  • Overloads the meaning of an existing keyword (technical)
  • In large classes, it's often confusing trying to figure out what a variable comes from (social)

A strong technical and strong social argument in favor. Two strong technical (imho) and a weak social argument.

Conclusion: tempting, but not as strong as sigils.


Warn on shadowing

slot $x;

method inc($x) {   # warning generated here
   ...
}

Pros:

  • Easy to implement (technical)
  • Follows existing practices (social)

Cons:

  • Doesn't prevent variable shadowing (technical)
  • Developers are often conditioned to ignore warnings (social)
  • Too many ways for warnings to be accidentally suppressed(technical)
  • How do we suppress this new warning? (technical+social)

The warning suppression merits some discussion. On the #cor IRC channel, it was suggested that we simply use the existing warning mechanism. Let's look at that.

slot $x;

no warnings 'shadowing';
method inc($x) {   # warning generated here
   ...
}

# thirty more methods

We have attached the warning, but now we have thirty more methods which might have this warning, but now we no longer see those warnings. We could tell the developer "if you suppress the warning in this way, move the method to the bottom of the class", but the developer might reasonably object to that.

So let's try it again.

slot $x;

{
    no warnings 'shadowing';
    method inc($x) {   # warning generated here
       ...
    }
}

# thirty more methods

Now we've lexically scoped the warning, but it's an ugly hack, similar to the way we used to fake state variables. Further, it's more grunt work the developer has to remember. Corinna is supposed to take that away, not add to it.

The grunt work might lead a developer to rename the argument, but in a large method, that would mean finding every instance of $x and figuring out if it's the instance method $x or the argument $x and that's more of the sort of grunt work we want to avoid. Ugly!

So let's try it again.

slot $x;

method inc($x) { 
    no warnings 'shadowing';
       ...
}

# thirty more methods

So that's kinda elegant, but we've put the warning after the variable doing the shadowing. I don't know of any other place in Perl we do something like this. It feels like a hack (and I don't know if it would work).

So looking at our original pros/cons liks, I see strong technical and social argument for. One strong technical argument against, and weak social and technical arguments against, but then we have the "suppression" argument. I really like the warnings idea, but I don't think it's working.


Other Ideas*

Variations of changing how signatures work.

class Point {
    slot ($x, $y) :param;

    method move (slot $x, slot $y) {
    }
}

Signatures are still experimental and I believe Dave Mitchell is still planning on doing work there. I do not believe we're at liberty to change signatures.

@duncand
Copy link

duncand commented Aug 15, 2021

About cons with twigils, I don't see Is somewhat controversial (social) as an actual con like the others, rather that's more of a self-referencing argument, it is controversial because it is controversial so doesn't have a place in logical decisions based on merits.

@duncand
Copy link

duncand commented Aug 15, 2021

@Ovid's summary of the options here makes me think that any option which prevents shadowing is the best choice, and the one with the twigils has the best huffman coding.

However I would also argue that if we're introducing twigils to prevent shadowing here, we should also introduce them in other places to prevent other kinds of shadowing.

For example, introduce that within a Corinna class declaration, any my $foo ALSO have twigils.

Also, conceptually, I see parameters as something that ought to have their own twigils too, because they are quite distinct in function from non-parameter lexicals.

In particular, and also compatible with the existing notion of @_ but I'm not basing it just on that, I consider the notion of the entire set of arguments to a routine being a collection-typed value, so its kind of like we have implicit $args which has a slot of its own for each parameter, and then the twigil is referencing it directly.

@Ovid
Copy link
Collaborator Author

Ovid commented Aug 15, 2021

@duncand I do not anticipate expanding twigils to my variables because that is far outside the scope of what we're doing and I see no benefit to doing so.

Second, we cannot add twigils in signatures because, as I noted above, work on signatures is ongoing. I seriously doubt P5P would look kindly on us unilaterally changing something this fundamental to the language.

But as usual, for both of those cases, if you feel there is an argument to be made, we need:

  • A clear problem description (including why it's a problem)
  • Proposed syntax showing a solution
  • Explanation of why existing tools do not solve the problem

Those would probably best be done in another ticket as this one is about addressing the shadowing issue.

@duncand
Copy link

duncand commented Aug 15, 2021

@duncand I do not anticipate expanding twigils to my variables because that is far outside the scope of what we're doing and I see no benefit to doing so.

When I say my I specifically mean things declared within the context of a Corinna class block that are outside a routine.

A better specific example would be Corinna class/static slots, common $x.

The original question about twigils may have implied that the decision would cover these too, but since it didn't say so explicitly, I'm saying that if twigils are used for object instance slots they should also be used for class static slots.

And possibly a different twigil from instance slots so its easier to tell them apart.

And a strong reason to do so is the cited reasons in favour of twigils, good at preventing shadowing or confusion.

@Ovid
Copy link
Collaborator Author

Ovid commented Aug 15, 2021

@oodler577 I just want to say that negative comments regarding borrowing ideas from Raku are not helpful because they are not technical arguments.

Nor, in fact, are they social arguments unless you can explain why Perl borrowing from Python (core OO), C, sed, awk, and many other languages is OK while borrowing from Raku is not.

You wrote:

If you want this to be successful (and I want to see it successful myself), it needs to tend towards Perl5. This is just what the last 20 years has shown us. Otherwise, you're just risking the reimplementation (or vulnerable to the accusation) of Perl 6/Raku.

Raku has been the creation of a new language that attempted to fix all of Perl's problems at once while extending the language into heretofore unknown (to Perl) directions.

Corinna is:

  • a very limited attempt to fix one single area of Perl
  • almost at an RFC stage
  • largely implemented via Object::Pad
  • P5P discussions seem to imply that P5P is receptive to the idea.

Comparing Corinna to Raku is comparing a Toyota Corolla to an F-35 stealth fighter.

Update: Honestly, there's a hell of a lot more I'd steal from Raku if I could, but the languages have evolved in different directions and much of the brilliance in Raku simply can't be done in Perl.

@Ovid Ovid mentioned this issue Aug 15, 2021
15 tasks
@salva
Copy link

salva commented Aug 15, 2021

IMO, the biggest issue with twigils is that in a lot of cases they are already valid syntax and so, perl would have to support two syntax: the backward compatible one and the new one with twigils (activated, for instance, after use v7). That, besides a technical issue, is also a social issue as it would require the programmer to take into account which mode is active while coding.

For instance, something I do frequently is to prefix debug statements with the process id as in:

say "$$:here";

Now, how is this going to be parsed under twigils rules, if $: is used as the slot prefix?

say($$ . ':here');  # or...
say(${$:here});

Are there other ambiguities like this one?

In summary, I think it is important to consider which specific twigil to pick for attributes and which kind of ambiguities it is going to introduce in the language in order to make a decision.

@Grinnz
Copy link

Grinnz commented Aug 15, 2021

Interpolation is known to be a case where it would be ambiguous. Since interpolation of these variables would be a primary goal, one option would be to require punctuation variables to be interpolated as "${:}" or similar. This would also be a much bigger problem with the twigil $. than one which is only used in formats like $: - another solution is disabling format punctuation variables in the scope of class.

@leonerd
Copy link
Collaborator

leonerd commented Aug 15, 2021

@Grinnz

This would also be a much bigger problem with the twigil $. than one which is only used in formats like $: - another solution is disabling format punctuation variables in the scope of class.

Is in fact why I originally suggested $:name rather than $.name`.

At this point I feel a lot of this discussion chain is rehashing old grounds already covered months ago.

@Grinnz
Copy link

Grinnz commented Aug 15, 2021

It is, but it was correct that this issue post didn't cover this caveat that has to be dealt with for twigils to work.

@duncand
Copy link

duncand commented Aug 15, 2021

Per the last few comments, it seems to me the biggest issue to resolve to adopt twigils is pick a specific syntax that is presently illegal syntax so that it wouldn't conflict with something that exists now and we can avoid the problematic option of having to use a feature flag to disambiguate.

@abraxxa
Copy link

abraxxa commented Aug 17, 2021

Comparing Corinna to Raku is comparing a Toyota Corolla to an F-35 stealth fighter.

I love that one as Toyotas are as reliable as Perl 5 but comparing Raku to an F-35 is unfair! 😉

@oodler577
Copy link

oodler577 commented Aug 17, 2021

@oodler577 I just want to say that negative comments regarding borrowing ideas from Raku are not helpful because they are not technical arguments.

Nor, in fact, are they social arguments unless you can explain why Perl borrowing from Python (core OO), C, sed, awk, and many other languages is OK while borrowing from Raku is not.

You wrote:

If you want this to be successful (and I want to see it successful myself), it needs to tend towards Perl5. This is just what the last 20 years has shown us. Otherwise, you're just risking the reimplementation (or vulnerable to the accusation) of Perl 6/Raku.

Raku has been the creation of a new language that attempted to fix all of Perl's problems at once while extending the language into heretofore unknown (to Perl) directions.

Corinna is:

  • a very limited attempt to fix one single area of Perl
  • almost at an RFC stage
  • largely implemented via Object::Pad
  • P5P discussions seem to imply that P5P is receptive to the idea.

Comparing Corinna to Raku is comparing a Toyota Corolla to an F-35 stealth fighter.

Update: Honestly, there's a hell of a lot more I'd steal from Raku if I could, but the languages have evolved in different directions and much of the brilliance in Raku simply can't be done in Perl.

TIme out, here. You wrote an aweful lot to counter and distract from my simple point; which I don't believe was missed. My point is simply this: if you are looking at concepts that are not famiar to journeymen Perl programmers (and indeed offer them as a natural progression), you will lose the people you need to make this successful. To do otherwise, and in particular, this case is to head in the direction of reimplementing Perl 6. Only this time you're going to reimplement it poorly. Is that negative? No, it's already bearing out. It's only "negative" if you don't like what I am stating. (update) struck out a part that was negative. I apologize for that. Point remains, a plea to keep this close and familiar so you can benefit as much of the current Perl 5 programmers as possible.

@leonerd
Copy link
Collaborator

leonerd commented Aug 18, 2021

There are two parts to this argument I haven't seen anyone else make yet, so here in the hope that this isn't at all a repeat.

Also, both of these points are made by reference to myself having actually written large amounts of real actual code using Object::Pad. [1].

Dynamic Method Generation

If twigil syntax exists, then methods that access slots can be dynamically added to classes by MOP access, by way of a :unbound method attribute. This tells the compiler not to resolve slot names at the time the method body is compiled:

  my $mref = method :unbound {
    $:aslot++;
  };

This "unbound method" is not yet useable, but can now be attached to a class by a MOP call, and only at that point we'll resolve the slot name in that class:

  $metaclass->add_slot( '$:aslot' );
  $metaclass->add_method( inc_slot => method :unbound { $:aslot++ } );

If instead, slot variables look identical to regular lexicals, then code of this form cannot be constructed. There would be no way to write the anonymous method {} reference in the first block of code above, without it failing to parse at compiletime because of an unrecognised variable name.

A mechanism does currently exist in Object::Pad to do this kind of thing, and it is Not Pretty:

  my $metaslot = $metaclass->add_slot( '$aslot' );
  $metaclass->add_method( inc_slot => sub {   # we can't use method
    my $self = shift;                         # because we're not method
    $metaslot->value($self)++;
  });

It turns what would be a simple efficient OP_SLOTPAD inside the method body into a closure capture of the $metaslot instance which invokes an lvalue accessor method on it at runtime, just to fetch the value of that slot. It works, but it's incredibly inefficient and removes most of the entire point of having these slot variables feel variable-like in the first place.

Surprising Lazy Initialization

One of the commonly-requested features from Moo(se?) users is "does it support lazy builders?". I have been hesitant to add these to Object::Pad itself for the reasons I explain below, but because people kept asking about it, I created a mechanism to allow 3rd-party attributes, and then created this:

https://metacpan.org/pod/Object::Pad::SlotAttr::LazyInit

This kind of thing is subtle to use in practice, because "slots look like lexicals". With Moo(se?), all instance data is accessed via accessor methods, and people are culturally used to the fact that methods can run code. They might print spurious warnings, or they might fail outright:

  use Moo;
  package OrangePeeler;
  
  has orange => ( is => 'lazy' );
  sub _build_orange {
    die "Not on a Tuesday, no" if (localtime)[6] == 2;
    warn "Maybe, maybe not" if rand > 0.5;
    return Orange->new;
  }

  sub peel {
    my $self = shift;

    # might die or warn but that's OK; we expect that
    my $orange = $self->orange;
    say "Peeling $orange...";
  }

Whereas, if all your slot variables look just like variables, then this might be considered surprising:

  use Object::Pad;
  class OrangePeeler;

  has $orange :LazyInit(_make_orange);
  method _make_orange {
    die "Not on a Tuesday, no" if (localtime)[6] == 2;
    warn "Maybe, maybe not" if rand > 0.5;
    return Orange->new;
  }

  method peel {
    # This variable interpolation itself might die or warn
    say "Peeling $orange...";
  }

By using a twigil and making slots "look different to variables", we can disarm this false feeling of safety, by reminding the programmer that these aren't just boring lexical variables, and that fun things may still happen:

  method peel {
    # This interpolation might die, but that's OK because it looks weird
    say "Peeling $:orange...";
  }

@davidnicol
Copy link

davidnicol commented Aug 18, 2021 via email

@borisdaeppen
Copy link

borisdaeppen commented Aug 18, 2021

Some views, questions and suggestions from a "Normal Perl User".

"Java-Style"

Why exactly is this "Java-Style" not possible, as mentioned by OP?
It seems to me that it would be a good way to go, just from a user-perspective.

class Point {
    has ($x, $y) :param;

    method move ($x, $y) {
        $self->x = $x;
        $self->y = $y;
    }
}

Is it because it is not technically feasible to distinguish internal access from setter/getter method? But how is this solved in Java, since they can do it?

Can't Perl figure this out behind the curtain?

  • if :writer is set, $self->x works just fine
  • if :writer is not set, can't Perl distinguish somehow that in case of $self, the slot/attribute $x is meant?
  • if :writer is set, and I want to access the variable directly... I can't... is this important?
Arrow Dollar

Could $self->$x work? I have not seen it in this thread.

class Point {
    has ($x, $y) :param;

    method move ($x, $y) {
        $self->$x = $x;
        $self->$y = $y;
    }
}

My guess is, this is conflicting with some Perl-Golf, but I'm not sure.
I would find it quite intuitive, it reads as "the variable from me", instead of "the function from me".

Twigils

It seems to me, that twigils are a whole conception, where there are a lot of different characters with different meanings. So as an e.g. Raku-User there is a general concept to learn, which you can then use in different circumstances, by also choosing from different special characters. Its' probably not good design to take just one single character from this concept to just build one Corinna-specific feature?
Also that :!attr as a twigil suggestion... the ! just "screams" to strong imho.

Dot Dot

I'm sad, that .. is already taken in Perl, because it would be UNIX-style for "up":

my $x = 12;
{
    my $x = 'hello world';

    say "inner x = $x";
    say "outer x = $..x";
}

Well... ok, maybe I'm not sad. Not that beautiful as I imagined :-)

@haarg
Copy link

haarg commented Aug 19, 2021

"Java-Style"

Why exactly is this "Java-Style" not possible, as mentioned by OP?
It seems to me that it would be a good way to go, just from a user-perspective.

class Point {
    has ($x, $y) :param;

    method move ($x, $y) {
        $self->x = $x;
        $self->y = $y;
    }
}

Is it because it is not technically feasible to distinguish internal access from setter/getter method? But how is this solved in Java, since they can do it?

Can't Perl figure this out behind the curtain?

* if `:writer` is set, `$self->x` works just fine

* if `:writer` is _not_ set, can't Perl distinguish somehow that in case of `$self`, the slot/attribute `$x` is meant?

* if `:writer` is set, and I want to access the variable directly... I can't... is this important?

Java method calls require parenthesis. This makes them distinct syntactically.

In perl this would mean overloading existing syntax with a lot of magic based on context, and would prevent many things that should work. Methods generates with :reader or :writer are not special. They are just shortcuts so you don't have to write the methods yourself. If a x method exists, how do you access the $x variable? How would you write a reader method manually if calling the method and accessing the variable use the exact same syntax?

Arrow Dollar

Could $self->$x work? I have not seen it in this thread.

class Point {
    has ($x, $y) :param;

    method move ($x, $y) {
        $self->$x = $x;
        $self->$y = $y;
    }
}

My guess is, this is conflicting with some Perl-Golf, but I'm not sure.
I would find it quite intuitive, it reads as "the variable from me", instead of "the function from me".

This is existing syntax for a method call. It will call the method name stored in the variable $x. It can't be overloaded without breaking existing syntax. And it would also require a lot of magic that would be confusing to document.

@borisdaeppen
Copy link

this would mean overloading existing syntax with a lot of magic based on context

hmmm... where have I seen this "context dependant magic" before..? it sounds so familiar, if not to say intimate 😍

@salva
Copy link

salva commented Aug 23, 2021

@leonerd

this kind of thing is subtle to use in practice, because "slots look like lexicals". With Moo(se?), all instance data is accessed via accessor methods, and people are culturally used to the fact that methods can run code. They might print spurious warnings, or they might fail outright:

I don't find this argument too compelling. There are already several mechanism supported by perl allowing one to run custom code when assigning to some variable (tie), or invoking simple operators like addition, stringification, etc. (overload). And well, everything using magic hooks.

@heikojansen
Copy link

I wasn't sure at first if non-scalar attributes were allowed by this RFC ... apparently they are, but what if they weren't: Could the whole twigil discussion be sidestepped by giving attributes their very own sigil instead?
Probably has a whole lot of different drawbacks but I think I haven't seen this option being discussed so far?

Please note that that wouldn't necessarily be my favorite, though: based on Ovids comment above, and the variants he describes, I'd prefer the twigil variant as the most readable (to me) one of them.

Though in the end I'll happily take whatever is chosen since it will be a huge step forward for Perl, anyway, and scratch my biggest itch (besides an "optional chaining" operator - like ?. in JavaScript - perhaps...).

@Ovid
Copy link
Collaborator Author

Ovid commented Feb 20, 2022

Closing this ticket. We won't do this for v1, but perhaps it will be revisited later.

@Ovid Ovid closed this as completed Feb 20, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests