-
Notifications
You must be signed in to change notification settings - Fork 19
Methods
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."
As with all of Cor, this is a work in progress and not set in stone.
In Cor, a method is declared using the method
keyword. Internally—typeglob
slots?—we mark methods as being methods and not subroutines. This ensures that
if we call $self->$method
, we get a method and not a subroutine we've
imported into our namespace.
Further, roles will no longer need to employ heuristics to determine what
methods they provide: only things declared with method
will be marked as
methods, so subroutines will never be exported. Consideration will be needed
for consumption of non-Corinna roles.
The structure of a method will be (faking a grammar):
METHOD ::= MODIFIERS 'method' SIGNATURE '{' (perl code) '}'
SIGNATURE ::= METHODNAME '(' current sub argument structure + extra work from Dave Mitchell ')'
METHODNAME ::= [a-zA-Z_]\w*
MODIFIERS ::= MODIFIER { MODIFIER }
MODIFIER ::= 'has' | 'private' | 'overrides' | 'multi' | 'abstract' | 'common'
This means that more than one modifier might be present:
common private method next_id () { ... }
class Some::Class {
method foo() {
$self->do_something;
...
}
}
Instance methods belong to instances of classes. Thus, calling Some::Class->foo would be an error. It might even be possible to trap some cases of that at compile time.
See also Class Data and Methods.
class Some::Class {
shared method foo() {
$class->do_something_else;
...
}
}
Class methods are methods which are shared across all instances of a class.
You may call a class method on an instance or a class name. Though
constructors in Perl are considering class methods (as Perl does not have a
new
keyword), we have other cases where we need class methods called. For
example, factory classes often have a constructor with a different name to
make it clear that you're not instantiating a normal class:
my $connection = Factory::Connection->create($type);
Of course, various helper methods for building an object, prior to object instantion, are also class methods.
There has been an objection to using shared
because the :shared
attribute
is used for sharing data structures between threads:
my $locked :shared;
Given that we also need class data, this is problematic, though we'd use the
shared
as a declarator and not an attribute:
shared $pi = 3.1415927;
Other suggestions welcome. class
was considered, but then we have this:
class Universe {
class $pi = 3.1415927;
class method big_bang ($theory) { ... }
}
Overloading the meaning of class
seems problematic. Further, if we want to
extend Corinna with nested classes, this will complicate parsing.
Possibilities:
-
shares
(too similar toshared
?) common
mutual
joint
collective
have
It's been pointed out that my
and our
are both possessive pronouns. For a
class, has
and shared
are analogous, but they are a verb and adjective
respectively. What if they were also possessive pronouns?
class Client {
its $ID;
their $next_ID;
its method newest () { $ID == $next_ID - 1 } # 'its' is optional
their method update_next_ID ($new_ID) {
$next_ID = $new_ID if $new_ID >= $next_ID;
}
}
Private methods are prefixed with private
:
private method do_not_touch () { ... }
Private methods may only be called inside the class or role they're declared in. If a class consumes a role, neither may access the other's private methods.
Note: for those who want Java-style "protected" methods that aren't available outside the class but may be called in a subclass, we don't anticipate supporting this because it's not really needed, but you can also use a leading underscore to "fake" it.
A method overrides a parent method if has the same name and accepts the same
number of arguments. We use the override
keyword for this:
override method execute_customer ($for_real) {
...
}
This instantly tells the person that there's a parent method. The parent method may or may not be called by the child method.
Shared methods may not override instance methods and vice-versa.
Using override
where there is no parent method should be a fatal,
compile-time error.
Failure to use override
when there is a parent method might be a
warning. This is open for discussion, but likely fails in the case of the
method being supplied via role composition. The rationale is simple: if you
inherit from a class and someone violates the contract by taking the parent
method away, you'd like to know that immediately.
That being said, it's likely that the use of the override
modifier would be
completely optional.
It's been proposed that we allow abstract classes via abstract
. If so,
allowing abstract methods with the same keyword seems appropriate:
abstract class Subclass::Me {
abstract method ($foo);
}
Abstract methods must be subclassed and can't be instantiated directly. Similarly, an abstract method must be overridden in a subclass (directly or via role consumption). Failure to do so should be a compile-time error.
This is the most controversial part of the proposal: multi methods.
Multiple dispatch via multimethods is typically done using a type system. For Cor, since Perl's type system puts data structures before data types, we instead rely on the number of arguments:
multi method authenticate($token) { ... }
multi method authenticate($user,$pass) { ... }
multi method authenticate($resource, $user, $pass) { ... }
This is not perfect (what if you want authenticate($resource,$token)
?), but
it has the advantage that many common cases in hand-written dispatch can be
skipped. Instead, the language itself handles the dispatching. The alternative
tends to be something like this:
method authenticate(@args) {
if ( 1 == @args ) {
$self->_token_auth(@args);
}
elsif ( 2 == @args ) {
$self->_user_pass_auth(@args);
}
elsif ( 3 == @args ) {
$self->_resource_auth(@args);
}
}
There are several issues with that.
- What happens if you have no arguments? Oops.
- What happens if you have more than 3 arguments? Oops.
- Error messages, if they exist, are invariably ad-hoc
- You have to write scaffolding code that's easy to get wrong in other ways
The failure to take into account the number of arguments is a very common bug when forced to resort to manual dispatching and the if/else chain can be hard to read (given/when is marginally better).
And you have to write _token_auth
, _user_pass_auth
, and _resource_auth
with separate names, even though they're semantically the same thing
(reasonable people disagree with whether or not this is a good thing).
Of course, you can get around some of this by sprinkling your code with more and more publc methods:
method token_auth($token) { ... }
method user_pats_auth($user,$pass) { ... }
method resource_auth($resource, $user, $pass) { ... }
Corinna takes the position you want your contract to be as small as possible.
Note: multi-examples are courtesy of Damian Conway.
Here's a quicksort implementation in Perl:
sub quicksort {
my ( $pivot, @list ) = @_;
return
!@_ ? ()
: !@list ? $pivot
: (
quicksort( grep { $_ < $pivot } @list ),
$pivot,
quicksort( grep { $_ >= $pivot } @list ),
);
}
It's a trivial example, but it might not be immediately clear to people what's happening. So let's see it with multi subs:
multi sub quicksort () { () }
multi sub quicksort ($single) { $single }
multi sub quicksort ($pivot, @list) { quicksort(grep {$_ < $pivot} @list),
$pivot,
quicksort(grep {$_ >= $pivot} @list)
}
And pure functional Mergesort becomes vastly more comprehensible:
sub merge ($x, $y) {
!@$x || !@$y ? ( @$x, @$y )
: $x->[0] < $y->[0] ? ( $x->[0], merge([$x->@[1..$#$x]], $y) )
: ( $y->[0], merge($x, [$y->@[1..$#$y]]) )
}
multi sub mergesort () {}
multi sub mergesort ($single) { $single }
multi sub mergesort (@list) { merge
[ mergesort @list[0..@list/2-1] ],
[ mergesort @list[@list/2..$#list] ]
}
And if you feel the need to build overloaded pseudo-constructors for classes:
class Iterator {
has $from = 0;
has $to = MAX_INT;
has $step = 1;
has $next = $from;
method next () {
return if $next > $to;
(my $curr, $next) = ($next, $next+$step);
return $curr;
}
multi shared method seq($to) {
$class->new(to=>$to-1)
}
multi shared method seq($from, $to) {
$class->new(from=>$from, to=>$to)
}
multi shared method seq($from, $then, $to) {
$class->new(from=>$from, to=>$to, step=>$then-$from)
}
}
$iter = Iterator->seq(10);
$iter = Iterator->seq(1 => 10);
$iter = Iterator->seq(1, 3 => 10);
Corinna—Bringing Modern OO to Perl