-
Notifications
You must be signed in to change notification settings - Fork 19
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
Visibility of fields in other instances #64
Comments
But that implies, that the compared $other object is of the same class. |
Should this be in the Discussions instead? They're available on this repo. |
Liskov implies that that overloading must be inherited (and can be overridden in some cases). Thus, this: use overload '==' => method ($other) {
$x == $other->x and $y == $other->y;
}; Must work even for a subclass because use overload '==' => sub ($self, $other) {
$self->{x} == $other->{x} and $self->{y} == $other->{y};
}; The above isn't not guaranteed to work with subclassing because the public interface of the subclass is not guaranteed to correspond to any particular internals: class PointDelta :isa(Point) {
field ( $dx, $dy ) :param;
method x { return $dx + $self->x }
method y { return $dy + $self->y }
} I think it's very important we focus on getting our interfaces correct before we start looking at optimizations. Update: Also, I realize you're talking about fields within the same class, but as @abraxxa points out, this requires asserting that |
Steve McConnell writes in Code Complete, 2nd Edition:
He also writes:
His book is incredibly well-researched and provides plenty of research to back up his assertions. We programmers are awful at guessing what's actually going to be our main issues. |
What about attributes without public readers? |
If the attribute doesn't have a public reader, it's not part of the API. Thus, subclasses won't worry about it, nor should related classes. Pseudocode: class Point {
has ($x, $y) :param; # no reader
# for the sake of argument, we assume that the $MOP is also injected
# into methods
user overload '==' => method ($other_point) {
# get corresponding $MOP
my $other_mop = $MOP->mop_for_same_class($other_point); # exception if not the same class
my $x2 = $other_mop->get_field('$x')->value; # guaranteed to exist
my $y2 = $other_mop->get_field('$y')->value; # guaranteed to exist
return ( $x == $x2 && $y == $y2 );
}
} The above is cumbersome and not well thought out, but the MOP is there to handle edge-cases. So I would suggest that we will need more work designing an effective MOP rather than injecting new language features into Corinna. Happy to hear counter-arguments. I see lots of code smells in my example. |
Hmm, I need more practical examples. I can't see the point (no pun intended) of a Thus, without seeing some kind of concrete use case, it's hard for me to come up with a better solution at this time. |
The original discussion was not about the API or about subclasses. It was about the current class being able to access fields from other instances. Any solution requiring the MOP that is not an extension or debugging tool is a failure IMO. A class should never have to use it against itself. Cloning is an example of an operation where you would want to modify the internals of another instance without providing any direct external access to the fields. |
I could see a solution to this being twigil-like. |
Could you define "extension" in this context? |
An extension to the object system itself, along the lines of MooseX modules. |
If it is planned to support serialization of objects, this serialization can be used to compare objects by serializing both and do a simple string eq. |
I think that since a class is an expert in its problem domain and thus an expert on its internals, it should be reasonable to allow a class to read the instance data of other instances of itself (not subclasses). After all, we can assume that if a class is instantiated, any other instantiations it encounters should be identical. However, that presupposes:
Another possibility is that a class can call private methods on other instances of itself, but that implies that those are effectively trusted methods and that violates encapsulation (a private method often changes internal state, but the control flow may be changed by another instance class calling that method, so this is a very bad idea). |
The simplest solution is to say that if a class wishes to compare itself to some other class, it must provide some public way of providing comparable data (provide a |
Regarding freeze/thaw (serializing/deserializing): we could use semantic versioning and only allow thawing of frozen state up to the same minor or major version. |
@abraxxa That requires the developer to manually add version numbers, doesn't it? |
I was still thinking class = package which has a version. |
Depending on the use case, you might need that recursively for all fields which are objects, and also for the class inheritance hierarchy and every role (BTDT).
In the current concept (and the current Object::Pad implementation), a role is of very limited use with regard to freeze/thaw. A role has no special access to object fields. A role can require a method from the consuming class to produce the class fields in a serializable way: this would also work for inheritance. But it does not work for fields from other roles consumed by the class unless each of these fields has a public reader method. The current MOP of Object::Pad, on the other hand, allows to examine any object because you can pass it an object instance. Therefore, you can just write a serializer in a separate piece of code and don't need to add it to a role. Passing another instance would also be required for a MOP-based comparison of two objects as drafted by @Ovid above. Compared to just making the relevant fields readable, this looks icky. |
The point of encapsulation is to ensure that the state of an object cannot be But the fields of another object are in the block scope of their shared class, From one (entirely reasonable) point of view, a declared field of any object However, from another (equally reasonable) point of view, fields are per-object Looking at it from another perspective, encapsulation is all about being able to But from the perspective of an individual object, the implied guarantee of its This, to my mind, quickly starts to become dubious from a software engineering In practice, of course, direct access to another object’s fields is only However, there are also practical issues. The most significant of which is, To see why this is an issue, let’s look at @leonerd’s example: use overload '==' => method ($other) {
$x == $other->x and $y == $other->y;
}; The parameter my $origin = Point->new( x=>0, y=>0 );
my $chromosome = DNA::Seq->new( pairs => \@seq );
if ($origin == $chromosome) {
say "Matched";
} This (one hopes!) would fail with a “No such method (x) in class DNA::Seq” But suppose we adopt some mechanism to allow direct field access. Now the equality operator code is: use overload '==' => method ($other) {
$x == $other!x and $y == $other!y;
}; and the exception is something like: “Cannot access field $x of class DNA::Seq However, the win comes at a cost. In order to produce that exception, every At least, until we can write: use overload '==' => method (Point $other) {
$x == $other!x and $y == $other!y;
}; ...and shift the checking and access-hardwiring to compile-time. All in all, I’m not entirely convinced that a slightly safer, but That said, I’m not completely opposed to the idea either...provided that And, of course, I’d be far more enthusiastic if Corinna also had |
@thoughtstream wrote:
I almost like this, but references are what's killing us every time. We would need deep cloning (which isn't always possible) and that is expensive. Copy-on-write would be lovely, but we don't have that and it's unlikely we'll get it soon. So we have no clean solution here. |
@Ovid noted:
Indeed. And since Corinna does not (as yet) allow |
@thoughtstream Actually, Object::Pad allows that and I'm pretty sure it's going to be in Corinna (but probably not with attributes). |
Thanks for the correction, @Ovid and @leonerd. However, I think my point still stands. If container fields take no attributes, then their contents can't be publicly initialized (via And @Ovid is quite right that this isn't even the most problematic case. Deep-copying such "container reference fields" (to ensure that they're read-only to other objects of the same class) would be feasible, albeit inefficient. But we're also going to see fields that contain references to closures, to methods, to filehandles, or to other objects. Deep-cloning those into immutability is not feasible at all. |
In the same spirit I'd expect that a class method would also be able to use fields of its objects? That would make sense for functions which operate on a list of objects (e.g. sort objects according to a field). |
Regarding the issue of cross-instance field access... at least in the case of comparison there is a possible alternative approach:
Similar technique can probably be leveraged in most other cases. Note there's no dynamic dispatch here, and no need to dynamically recover fields individually or by name. There's (hopefully) a runtime check on $other but you needed that anyway. |
A blog post will be coming later, but for now ... I ported two-decade old code to use the new object-oriented syntax for Perl. This is a modestly popular module and at least 34 other CPAN distributions use it. I often find it being used at client sites. In porting it, I learned the Corinna spec is flawed because I couldn't port it cleanly. Oh shit! I am the lead designer of Corinna. Years of effort, building on top of extensive Perl history, taking the knowledge gleaned from Moo/se and many other OOP alternatives, books read, articles read, and an entire design team putting this together, but there are still some pretty glaring conceptual design flaws in Corinna. How did we screw up so badly? After a couple of hours of writing and rewriting the explanation of what Corinna did wrong, including exploring ways of fixing the issue, I discovered Corinna is not flawed. It was my original OO design that was broken. Badly. The design of Corinna exposed design flaws in my code that I had never seen before. Many Perl developers are going to discover they don't actually understand class-based OOP (including me, apparently). I've already gotten grief from people who are pissed that OOP best practices that have been accepted for decades are provided in Corinna. I expect more grief in the future. |
We've already talked lots on the contentious subject of whether subclasses can see fields of their parent class (#61, etc...). Lets ignore that for now and just stick to fields within the same class.
It would be useful if certain code within a class could see fields of other instances, within that class. Primarily this is useful for things like operators.
Consider our dear old standard friend, the simple 2D point:
We'd like to write an equality test operator for this. It could easily be written using those
:reader
methods:It is a little unsatisfying though to have to use the public reader API to access these fields. It relies on us always having those methods, and it means that every test requires two more dynamic method dispatches.
Compare to the way you'd probably write this in classical perl, which doesn't need any method invocations at all:
I can't help thinking we might be missing a trick here. Some nice way we could write this better.
The text was updated successfully, but these errors were encountered: