-
Notifications
You must be signed in to change notification settings - Fork 19
Slot Semantics
Please see the main page of the repo for the actual RFC. As it states there:
Anything in the Wiki should be considered "rough drafts."
Click here to provide feedback.
After a bit of discussion on #cor, it turns out that having slot declarations being lexical and using the slot identifier name as the public name is causing some issues.
Slots are private (which works in isolation), but NEW constructor needs to be able to "see" the slots in all of the lexical scopes if they have :new (unless NEW is implicitly in every class).
But what do we do for this?
class Parent {
has $x :isa(Int) :new;
}
class Child isa Parent {
has $x :isa(Str) :new;
}
In the above, we have a few issues.
First, if I call my $o = Child->new( x => 'bob')
, what does the Child $x
get? Internally, we have to build classes from the bottom to the top of the
heirachy and the parent would get the child's type of the slot.
We also have similar issues with roles. There other issues, but that's a clear one that exposes all of the issues.
It seems like the problem here is that the variable holding the slot and the external name of the slot are tightly coupled.
The proposal to fix this is to decouple the name of the variable containing
the slot and the attribute for the slot (similar to init_arg
in Moo/se, but
not quite the same).
I think this can be done with a new attribute called :name($identifier)
.
class Parent {
# without a :name(...) attribute, the name is 'x'
has $x :isa(Int) :new;
}
class Child isa Parent {
has $x :isa(Str) :name(some_x) :new;
}
Now in the above, to construct it:
my $object = Child->new(
x => 42,
some_x => 'DNA',
);
This means that for roles, we'd do the same thing. The slot variable would be lexically scoped, but the slot name would determine the name by which we read/write/construct the slot data.
We would need, for children overriding parent slot methods, to determine the
exact behavior and syntax. A simple :has(+name)
might work, with similar
semantics to Moose?
A child could override the parent slot names, but role slot names would require the standard role exclusion and aliasing behaviors.
By default, builders are lazy. This means they will not be executed until such time that the value for that slot is needed:
has $cache :builder;
method _build_cache () {
return Hash::Ordered->new;
}
For the above, what if we needed the cache instantiated at the same time the
instance is instantiated? You can either supply an :immediate
attribute,
or (because the code is simple enough), you can inline the construction with
=
:
has $cache = Hash::Ordered->new;
Let's consider a simple Box
class:
class Box {
has ( $height, $width, $depth ) :new :reader :isa(PositiveNum);
has $volume :reader :builder;
# if you leave off 'private', this can be overridden in a subclass
private method _build_volume () {
return $height * $width * $depth;
}
}
In the above, _build_volume
is guaranteed to have defined values for
$height
, $width
, and $depth
. However, in Cor, we assume that slot values
will be calculated in the order listed in the code: So we could make the
above code even simpler:
class Box {
has ( $height, $width, $depth ) :new :reader :isa(PositiveNum);
has $volume :reader = $height * $width * $depth;
}
Because the $height
and similar variables are guaranteed to be calculated
first, $volume
is guaranteed to have access to those values.
Any object destruction will destroy all values in the reverse order. Thus, if you need your database handle destroyed near the end of the object destruction cycle, put it near the top of your list of slot declarations.
(Note: I'm somewhat uncomfortable using slot definition ordering as meaningful, but making it deterministic in this manner is at least clearly understood).
Corinna—Bringing Modern OO to Perl