Closed Ovid closed 2 years ago
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.
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.
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.
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.
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 packageself
.$self:x
could be part of a ternary.$self.x
is concatenating$self
with the return from calling the subx
. 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.
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.
@duncand A difference between twigils and your proposed ->>
, is that twigils will still interpolate nicely into strings.
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.
2) 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.
@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.
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 Those are both reasonable, but I needed to have an example which showed the inadvertent data hiding.
https://docs.raku.org/language/variables is useful for 2 reasons:
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.
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 ...)
@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 ).
@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?
@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.
@haarg you're grossly missing my point, but I digress
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)
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 😅
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'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.
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.
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.
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.
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.
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.
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.
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
.
This doc section may also be relevant
I feel we're overly focusing on the ambiguity between slots and method arguments with the same names. Twigils fix that problem, which is wonderful, but that's not the main reason why I want them in Corinna.
To me, the biggest benefit of twigils is the ability to tell variables from slots at a glance. Slots aren't normal variables, they're special, so it makes sense to make them look different. It's also very perlish. After all, concise explicitness is a distinguishing feature of Perl, sigils and the separation of numeric/stringy operators are good examples of that.
I feel we're overly focusing on the ambiguity between slots and method arguments with the same names. Twigils fix that problem, which is wonderful, but that's not the main reason why I want them in Corinna.
To me, the biggest benefit of twigils is the ability to tell variables from slots at a glance. Slots aren't normal variables, they're special, so it makes sense to make them look different. It's also very perlish. After all, concise explicitness is a distinguishing feature of Perl, sigils and the separation of numeric/stringy operators are good examples of that.
Twigils can be useful for a common shorthand but the more general solution needs to work for both $self and $other objects of the current class to directly reference slots that have no :reader or :writer defined.
So I suggest that a general form could be like $self!x
and $other!y
and then $!x
would be a short-hand for the first and the short-hand wouldn't exist for the second.
The general form would need to take any arbitrary expression and combine it with a slot lookup, so <any expression here> !x
for example. Since slots are always private we know at compile/parse time that the !x
or whatever is a slot for the current class and so it expects that the expression to its left would evaluate to an object of the current class, though in the general case that would possibly fail at runtime if it isn't actually an object of the current class when that is evaluated.
What about this?
my $x = 12;
{
my $x = 'hello world';
say "inner x = $x";
say "outer x = $.x";
}
We don't need that why would we need this abomination for Cor? That idea and need is already confusing to me - why does scope work different in methods? Just don't name variables in your method the same as slots in the object if that method needs to access the object slot.
I'm excited about Cor because it "looks and feels" like Perl, something other systems like Moose or Mojo do not (to me). Adding this completely not obvious twigils just make the code harder to read for no real gain (IMHO) - but I'm just a hobbyist.
Problems become 3 parts:
I believe the best way to design this is to do something that directly mirrors the current blessed hashref approach, which is that you ALWAYS reference a slot in terms of a subscript of an object of the type.
The blessed hash way:
$self->{x}
$other->{y}
(any appropriate value expression)->{z}
The Corinna way:
$self->!x
$other->!y
(any appropriate value expression)->!z
Or something like that.
When I say that slots should have their own namespace, the above is what I really mean, each object such as $self or $other IS the namespace for the slots, and all other kinds of Perl variables continue to scope/behave as they did before.
We really don't need twigils, but we do need the "->" alternative to mean its a slot direct access, whereas plain -> would apply to a defined :reader etc if and only if they exist.
Another syntax that can be repurposed for accessing the slots is the quote (currently an alias for ::
):
class Point {
has ($x, $y) :param;
method foo ($x) {
$self'x = $x;
}
method bar($other) {
$x += $other'x;
$y += $other'y;
}
}
Though, I am not sure it is going to be completely unambiguous when used in more complex code structures. For instance:
$x->{foo}'bar;
String interpolation can be an issue too: "hello '$name'ssss"
Another syntax that can be repurposed for accessing the slots is the quote (currently an alias for
::
):
I find it distasteful to use string delimiting characters for any purpose besides string delimiting, so no use of double quotes, single quotes, or backpacks for anything except delimiting strings.
Having given this further thought, I concede that at the moment its hard to think of good examples where both of the following are true at the same time for a given class Foo:
A method of Foo wants to access a slot S for any other object of the Foo class besides the $self object.
Slot S doesn't have any :reader or :writer or other custom public accessor method because it isn't okay for non-Foo classes to see it.
Therefore with respect to the MVP of Corinna I withdraw my objection for the direct access mechanism to slots of Foo being only available for the $self object.
For the MVP I consider it a reasonable compromise to have to declare a public :reader etc method for situations where a method needs to access slots of $other in addition to $self.
@duncand,
A method of Foo wants to access a slot S for any other object of the Foo class besides the $self object.
It is pretty common to have functions that take two arguments of the same class. For instance, any class implementing mathematical objects for which some kind of algebra exits (vectors, quaternions, sets, etc.).
For instance, a class implementing sets providing an intersection
method that takes $self
and $other
as arguments. In order to calculate the intersection efficiently you may like to access the internal representation of both objects without being restricted to accessing $other
just through its public interface.
A few other points of note, from my docs on this:
Compare in classical perl OO, with syntax highlighting in editors/etc the $self->{...}
variables are easy to see because of highlighting:
A simple use of Object::Pad syntax as it currently stands makes them much harder to see because they look identical to lexicals.
The use of a unique twigil, such as $:name
would make these stand out more - both in plain syntax to a human reader, and also make simple syntax highlighters able to much easier call them out as looking different.
(see also the screenshots in my doc: https://docs.google.com/document/d/1d62U2Z3w8zUnzg2QVxuGN6gtCoKATuiW1SCCSVRVIgc/edit#heading=h.a41lkp4lj35)
Additionally, "unbound methods" with syntactically unambiguous slot accesses inside could solve the question of how a MOP API can add method bodies which refer to such slots. An :unbound
attribute could permit the parser to compile a prototype method body unbound to a particular class that can use any slot name, which only becomes checked (and presumably resolved into representation indices) at the time it is bound into a class: Hypothetically allowing such code as:
package NotAClass;
sub import {
my $class = Object::Pad::MOP->create_class( "Ball" );
$class->create_slot( '$:colour' );
$class->create_method( paint => method :unbound {
say "Will now paint $self in colour $:colour";
} );
}
as without this ability it becomes difficult to write method bodies.
@leonerd,
I think it is reasonable to not have syntax sugar for cases where you are already using the MOP.
IMO, requiring a more convoluted way for accessing slots could be acceptable. For instance:
$class->create_method( paint => sub ($self) {
my \$colour = $class->get_ref_to_instance_slot($self, "colour");
say "Will now paint $self in colour $colour";
} );
That means the method you created would be significantly slower, which would in most cases mean nobody would use the MOP for this.
That means the method you created would be significantly slower, which would in most cases mean nobody would use the MOP for this.
If you are serious about the MOP, whatever syntax sugar you use would result at the end into a call like the one above. Every time an slot is referenced from a method you have to perform a lookup (or do it when entering the method and cache it as it is done in the code above).
At most, you could use a faster lookup method for the common case (i.e. default storage), but then, you could also use that shortcut in the code above.
People aren't "serious about the MOP", they are serious about solving their task in a way that works. The task of this design is to make the way that works natural to use and huffman coded.
@duncand,
A method of Foo wants to access a slot S for any other object of the Foo class besides the $self object.
It is pretty common to have functions that take two arguments of the same class. For instance, any class implementing mathematical objects for which some kind of algebra exits (vectors, quaternions, sets, etc.).
For instance, a class implementing sets providing an
intersection
method that takes$self
and$other
as arguments. In order to calculate the intersection efficiently you may like to access the internal representation of both objects without being restricted to accessing$other
just through its public interface.
@salva Yes, I am keenly aware of such common cases to use $self plus $other, the math-like things are what I had in mind.
But my first sentence both of the following are true at the same time is the key here, and you didn't counter that.
I figure that in such objects it would be normal that you'd want any class to be able to see the same slots, say to read out the elements of the vectors etc, and so sufficient public accessors would be provided to read them such as with :reader.
Of course I would still rather have what I argued for originally, direct access to all objects, but I realized for an MVP the public accessors should be sufficient, so I won't push it so much.
@duncand,
But my first sentence both of the following are true at the same time is the key here, and you didn't counter that.
Yes, I was thinking about that too.
You have an internal representation for your objects that you would like to access when implementing some algorithm combining two objects of the same class.
For instance:
add
operator efficiently.@duncand,
But my first sentence both of the following are true at the same time is the key here, and you didn't counter that.
Yes, I was thinking about that too.
You have an internal representation for your objects that you would like to access when implementing some algorithm combining two objects of the same class.
For instance:
- A class representing a tree or a queue and a method to merge two of them. In order to do that efficiently you usually need access to the internal representation.
- A matrix or tensor class where data is stored packed as NVs plus the dimensions and some internal house keeping values (i.e. offsets and stripe sizes). You don't want to make that internal data public, but you need it in order to implement an
add
operator efficiently.
I have 2 main points to answer this:
To properly solve the general version of this we need something like Raku's "trusts" because in the general case there are MULTIPLE classes involved in the internal representation of a data structure and any one of those classes, or additional classes, should be able to access their members directly. An example I've mentioned in the past is a graph data structure including separate Node and Side classes plus a class representing an entire Graph. Simply being able to see $self and $other slots isn't enough, its only a half-measure.
A workaround for the MVP I would settle for which I might have to do anyway for that multi-class graph example even if private works normally, but can also work if you don't get $other, is to declare "public" accessor methods which are named such that it clearly documents they aren't part of the normal public API and are just part of the internal implementation, for example the method could be named "internal__foo" or such. Assuming the internal properties in question are reference types these can return direct references to them, and then the caller can work with them as if they had direct access to the slots.
In Corinna, you can write this:
But instead of moving the point via differences, what if you wanted to just pass the new x and y values?
You can't do that because the local variables
$x
and$y
hide the instance variables. In Java, you could do this:Corinna's semantics are sufficiently different from Java's that we don't have this option. What are our options?
The twigils might look like this:
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
: