This page was generated at 2010-03-10 07:02:30 GMT.
(syn r30019, pugs-smoke 19912)
[ Index of Synopses ]
Synopsis 13: Overloading
Larry Wall <larry@wall.org>
Created: 2 Nov 2004
Last Modified: 2 Feb 2010
Version: 13
This synopsis discusses those portions of Apocalypse 12 that ought to have been in Apocalypse 13.
The overloading mechanism of Perl 5 has been superseded by Perl 6's multiple dispatch mechanism. Nearly all internal functions are defined as multi subs or multi methods on generic types. Built-in operators are merely oddly named functions with an alternate call syntax. All you have to do to overload them is to define your own multi subs and methods that operate on arguments with more specific types.
From t/spec/S02-builtin_data_types/array_mmd.t lines 8–50 (no results): (skip)
| # L<S13/Multiple dispatch/"All you have to do to overload them is to define your own multi">
|
|
|
| my $here;
|
|
|
| #?rakudo skip 'defining operators'
|
| {
|
| my @a;
|
| $here = 0;
|
| multi infix:<..> ( Int $a, Int $b ) { $here++ }
|
| @a = 1..2;
|
| is $here, 1, "range operator was redefined";
|
| }
|
|
|
| #?rakudo skip 'defining builtins'
|
| {
|
| my @a;
|
| $here = 0;
|
| multi push (@a, *@data ) { $here++ }
|
| push @a, 2;
|
| is $here, 1, "push operator was redefined";
|
| }
|
|
|
| #?rakudo skip 'parsefail with the :<[]>'
|
| {
|
| my @a;
|
| $here = 0;
|
| multi postcircumfix:<[]> ( *@a ) { $here++ }
|
| my $x = @a[1];
|
| #?pugs todo 'bug'
|
| is $here, 1, "slice fetch was redefined";
|
| }
|
|
|
| #?rakudo skip 'parsefail with the :<[]>'
|
| {
|
| my @a;
|
| $here = 0;
|
| multi postcircumfix:<[]> ( *@a ) { $here++ }
|
| @a[1] = 0;
|
| #?pugs todo 'bug'
|
| is $here, 1, "slice store was redefined";
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
For unary operators, this makes little effective difference, but for binary operators, multiple dispatch fixes the Perl 5 problem of paying attention only to the type of the left argument. Since both argument types are used in deciding which routine to call, there is no longer any trickery involving swapping the arguments to use the right argument's type instead of the left one. And there's no longer any need to examine a special flag to see if the arguments were reversed.
For much more about multiple dispatch, see S12.
There is no longer any special use overload syntax separate from the declarations of the multi routines themselves. To overload an existing built-in sub, say something like:
multi sub uc (TurkishStr $s) {...}
A multi is automatically exported if goverened by a proto that is exported. It may also be explicitly exported:
multi sub uc (TurkishStr $s) is exported {...}
Now if you call uc() on any Turkish string, it will call your function rather than the built-in one.
The types of the parameters are included in the longname of any multi sub or method. So if you want to overload string concatenation for Arabic strings so you can handle various ligatures, you can say:
multi sub infix:<~>(ArabicStr $s1, ArabicStr $s2) {...}
multi sub infix:<~>(Str $s1, ArabicStr $s2) {...}
multi sub infix:<~>(ArabicStr $s1, Str $s2) {...}
The use overload syntax had one benefit over Perl 6's syntax in that it was easy to alias several different operators to the same service routine. This can easily be handled with Perl 6's aliasing:
From t/spec/S13-syntax/aliasing.t lines 7–33 (no results): (skip)
| # L<S13/Syntax/This can easily be handled with PerlĀ 6's aliasing>
|
|
|
| class Foo {
|
| method bar() { 42 }
|
| method bar_ref() { &bar }
|
| }
|
|
|
| {
|
| my $foo = Foo.new;
|
| lives_ok { $foo.bar_ref }, "returning a method reference works";
|
| }
|
|
|
| class Baz {
|
| method bar() { 42 }
|
| our &baz ::= &bar;
|
| }
|
|
|
| {
|
| my $ret;
|
| lives_ok {
|
| my $baz = Baz.new;
|
| $ret = $baz.baz();
|
| }, "calling an aliased method worked";
|
| is $ret, 42, "the aliased method returned the right thing";
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
multi sub unimpl (MyFoo $x, MyFoo $y) { upchuck(); }
&infix:<+> ::= &unimpl;
&infix:<-> ::= &unimpl;
&infix:<*> ::= &unimpl;
&infix:</> ::= &unimpl;
That's one solution, but often your alternatives all have the same name, and vary instead in their signature. Some operators are commutative, or can otherwise take their arguments in more than one order. Perl allows you to declare multiple signatures for a given body, and these will be pattern matched as if you had declared separate multi entries. If you say:
From t/spec/S06-signature/multiple-signatures.t lines 9–34 (no results): (skip)
| # L<S13/Syntax/"Perl allows you to declare multiple signatures for a
|
| # given body">
|
|
|
| # normal subs
|
| {
|
| multi sub si (Str $s, Int $i)
|
| | (Int $i, Str $s) {
|
| die "dispatch went wrong" unless $s ~~ Str && $i ~~ Int;
|
| "s:$s i:$i";
|
| }
|
| is si("a", 3), "s:a i:3", 'sub with two sigs dispatches correctly (1)';
|
| is si(3, "b"), "s:b i:3", 'sub with two sigs dispatches correctly (2)';
|
| }
|
|
|
| # try it with three sigs as well, and mixed named/positionals
|
| {
|
| multi sub three (Str $s, Int $i, Num :$n)
|
| | (Int $i, Str :$s, Num :$n)
|
| | (Num :$s, Int :$i, Str :$n) {
|
| "$s $i $n";
|
| }
|
| is three('abc', 3, :n(2.3)), 'abc 3 2.3', 'multi dispatch on three() (1)';
|
| is three(4, :s<x>, :n(2.3)), 'x 4 2.3', 'multi dispatch on three() (2)';
|
| is three(:i(4), :s(0.2), :n('f')), '0.2 4 f', 'multi dispatch on three() (3)';
|
| }
|
|
|
Highlighted:
small|full
multi sub infix:<+> (Us $us, Them $them) |
(Them $them, Us $us) { myadd($us,$them) }
that's equivalent to:
multi sub infix:<+> (Us $us, Them $them) { myadd($us,$them) }
multi sub infix:<+> (Them $them, Us $us) { myadd($us,$them) }
except that there really is only one body. If you declared a state variable within the body, for instance, there would only be one of them.
From t/spec/S13-overloading/multiple-signatures.t lines 6–32 (no results): (skip)
| # L<S13/"Syntax"/"If you declared a state variable within the body, for instance, there would only be one of them.">
|
|
|
| # test the general multiple signatures functionality
|
| class Base {has $.value is rw;}
|
| class Exponent {has $.value is rw;}
|
|
|
| multi sub infix:<+> (Base $b, Exponent $e) |
|
| (Exponent $e, Base $b) {$b.value ** $e.value}
|
|
|
| my $base = Base.new();
|
| my $exp = Exponent.new();
|
| $base.value = 2;
|
| $exp.value = 5;
|
|
|
| is($base + $exp, 32, 'First order works');
|
| is($exp + $base, 32, 'Second order works');
|
|
|
| # specifically make sure that there is only one state variable
|
| # this tells us that there is only one multi sub body
|
| multi sub postfix:<!> (Base $x) |
|
| (Exponent $x) {state $counter = 0; return ++$counter;}
|
|
|
| is($base!, 1, 'shared routine test 1');
|
| is($exp!, 2, 'shared routine test 2');
|
| is($base!, 3, 'shared routine test 3');
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
From t/spec/S06-signature/multiple-signatures.t lines 35–47 (no results): (skip)
| # L<S13/Syntax/"except that there really is only one body">
|
|
|
| {
|
| multi sub count (Str $s, Int $i)
|
| | (Int $i, Str $s) {
|
| state $x = 0;
|
| ++$x;
|
| }
|
| is count("a", 3), 1, 'initialization of state var in multi with two sigs';
|
| is count("a", 2), 2, 'state var works';
|
| is count(2, 'a'), 3, '... and there is only one';
|
| }
|
|
|
Highlighted:
small|full
A multi is in effect only within the scope in which it is defined or imported. Generally you want to put your multi subs into a package that will be imported wherever they are needed.
When you use the multiple signature syntax, the alternate signatures must all bind the same set of formal variable names, though they are allowed to vary in any other way, such as by type, or by which parameters are considered optional or named-only or slurpy. In other words, the compiler is allowed to complain if any of the alternatives omits any of the variable names. This is intended primarily to catch editing errors.
From t/spec/S06-signature/multiple-signatures.t lines 48–61 (no results): (skip)
| # L<S13/Syntax/"must all bind the same set of formal variable names">
|
|
|
| {
|
| eval_dies_ok q[ multi sub x ($x, $y) | ($x, $y, $z) { 1 }],
|
| 'multis with multiple sigs must have the same set of formal variables';
|
| eval_dies_ok q[ multi sub x ($x, $y) | ($x, @y) { 1 }],
|
| 'multis with multiple sigs must have the same set of formal variables';
|
| }
|
|
|
| # common sense
|
| eval_dies_ok q[ only sub y (Int $x, Str $y) | (Str $x, Int $y) ],
|
| 'and "only" sub can not have multiple signatures';
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
Conjectural: If the first parameter to a multi signature is followed by an invocant colon, that signature represents two signatures, one for an ordinary method definition, and one for the corresponding multi definition that has a comma instead of the colon. This form is legal only where the standard method definition would be legal, and only if any declared type of the first parameter is consistent with $?CLASS.
Dispatch is based on a routine's signature declaration without regard to whether the routine is defined yet. If an attempt is made to dispatch to a declared but undefined routine, Perl will redispatch to an AUTODEF submethod [conjectural] as appropriate to define the routine. This provides a run-time mechanism for fallbacks. By default, these declarations are taken at face value and do not specify any underlying semantics. As such, they're a "shallow" interpretation.
However, sometimes you want to specify a "deep" interpretation of your operators. That is, you're specifying the abstract operation, which may be used by various shallow operators. Any deep multi declarations will be "amplified" into all the shallow operators that can be logically based on it. If you say:
multi sub infix:<%> (Us $us, Them $them) is deep { mymod($us,$them) }
then
multi sub infix:<%=> (Us $us, Them $them) { $us = $us % $them }
is also generated for you (unless you define it yourself). The mappings of magical names to sub definitions is controlled by the %?DEEPMAGIC compiler hash. Pragmas can influence the contents of this hash over a lexical scope, so you could have different policies on magical autogeneration. The default mappings correspond to the standard fallback mappings of Perl 5 overloading.
From t/spec/S13-overloading/fallbacks-deep.t lines 6–23 (no results): (skip)
| # L<S13/"Fallbacks"/"is also generated for you (unless you define it yourself).">
|
|
|
| class Base {has $.value is rw;}
|
| class Exponent {has $.value is rw;}
|
|
|
| multi sub infix:<+> (Base $b, Exponent $e) is deep {$b.value ** $e.value}
|
|
|
| my $base = Base.new();
|
| my $exp = Exponent.new();
|
| $base.value = 2;
|
| $exp.value = 5;
|
|
|
| is($base + $exp, 32, 'defining infix:<+> works');
|
|
|
| $base += $exp;
|
| is($base, 32, 'is deep generates infix:<+=> on infix:<+>');
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
These deep mappings are mainly intended for infix operators that would have difficulty naming all their variants. Prefix operators tend to be simpler; note in particular that
multi prefix:<~> is deep {...}
is better written:
method Str {...}
(see below).
A class may define methods that allow it to respond as if it were a routine, array, or hash. The long forms are as follows:
method postcircumfix:<( )> ($capture) {...}
method postcircumfix:<[ ]> (**@slice) {...}
method postcircumfix:<{ }> (**@slice) {...}
From t/spec/S13-overloading/typecasting-long.t lines 6–58 (no results): (skip)
| # L<S13/"Type Casting"/"method postcircumfix:<{ }> (**@slice) {...}">
|
| # basic tests to see if the methods overload correctly.
|
|
|
| {
|
| my multi testsub ($a,$b) {
|
| return 1;
|
| }
|
| my multi testsub ($a) {
|
| return 2;
|
| }
|
| my multi testsub () {
|
| return 3;
|
| }
|
| class TypeCastSub {
|
| method postcircumfix:<( )> ($capture) {return 'pretending to be a sub ' ~ testsub(|$capture) }
|
| }
|
|
|
| my $thing = TypeCastSub.new;
|
| is($thing(), 'pretending to be a sub 3', 'overloaded () call works');
|
| is($thing.(), 'pretending to be a sub 3', 'overloaded .() call works');
|
| is($thing.(1), 'pretending to be a sub 2', 'overloaded .() call works');
|
| is($thing.(1,2), 'pretending to be a sub 1', 'overloaded .() call works');
|
| }
|
|
|
| {
|
| class TypeCastArray {
|
| method postcircumfix:<[ ]> (*@slice) {return 'pretending to be an array'}
|
| }
|
|
|
| my $thing = TypeCastArray.new;
|
| is($thing[1], 'pretending to be an array', 'overloaded [] call works');
|
| is($thing[2,3], 'pretending to be an array', 'overloaded [] call works (slice)');
|
| is($thing.[4], 'pretending to be an array', 'overloaded .[] call works');
|
| is($thing.[5,6], 'pretending to be an array', 'overloaded .[] call works (slice)');
|
| }
|
|
|
| {
|
| class TypeCastHash {
|
| method postcircumfix:<{ }> (*@slice) {return 'pretending to be a hash'}
|
| }
|
|
|
| my $thing = TypeCastHash.new;
|
| is($thing{'a'}, 'pretending to be a hash', 'overloaded {} call works');
|
| is($thing{'b','c'}, 'pretending to be a hash', 'overloaded {} call works (slice)');
|
| is($thing.{'d'}, 'pretending to be a hash', 'overloaded .{} call works');
|
| is($thing.{'e','f'}, 'pretending to be a hash', 'overloaded .{} call works (slice)');
|
| is($thing<a>, 'pretending to be a hash', 'overloaded <> call works');
|
| is($thing<b c>, 'pretending to be a hash', 'overloaded <> call works (slice)');
|
| is($thing.<d>, 'pretending to be a hash', 'overloaded .<> call works');
|
| is($thing.<e f>, 'pretending to be a hash', 'overloaded .<> call works (slice)');
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
Those are a bit unwieldy, so you may also use these short forms:
method &.( $capture ) {...}
method @.[ **@slice ] {...}
method %.{ **@slice } {...}
From t/spec/S13-overloading/typecasting-mixed.t lines 6–33 (no results): (skip)
| # L<S13/"Type Casting"/"method %.{ **@slice } {...}">
|
| # basic tests to see if the methods overload correctly.
|
|
|
| {
|
| class TypeCastMixed {
|
| method &.( |$capture ) {return 'pretending to be a sub'}
|
| method postcircumfix:<[ ]> (**@slice) {return 'pretending to be an array'}
|
| method %.{ **@slice } {return 'pretending to be a hash'}
|
| }
|
|
|
| my $thing = TypeCastMixed.new;
|
| is($thing(), 'pretending to be a sub', 'overloaded () call works');
|
| is($thing.(), 'pretending to be a sub', 'overloaded .() call works');
|
| is($thing[1], 'pretending to be an array', 'overloaded [] call works');
|
| is($thing[2,3], 'pretending to be an array', 'overloaded [] call works (slice)');
|
| is($thing.[4], 'pretending to be an array', 'overloaded .[] call works');
|
| is($thing.[5,6], 'pretending to be an array', 'overloaded .[] call works (slice)');
|
| is($thing{'a'}, 'pretending to be a hash', 'overloaded {} call works');
|
| is($thing{'b','c'}, 'pretending to be a hash', 'overloaded {} call works (slice)');
|
| is($thing.{'d'}, 'pretending to be a hash', 'overloaded .{} call works');
|
| is($thing.{'e','f'}, 'pretending to be a hash', 'overloaded .{} call works (slice)');
|
| is($thing<a>, 'pretending to be a hash', 'overloaded <> call works');
|
| is($thing<b c>, 'pretending to be a hash', 'overloaded <> call works (slice)');
|
| is($thing.<d>, 'pretending to be a hash', 'overloaded .<> call works');
|
| is($thing.<e f>, 'pretending to be a hash', 'overloaded .<> call works (slice)');
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
From t/spec/S13-overloading/typecasting-short.t lines 6–47 (no results): (skip)
| # L<S13/"Type Casting"/"method %.{ **@slice } {...}">
|
| # basic tests to see if the methods overload correctly.
|
|
|
| {
|
| class TypeCastSub {
|
| method &.( |$capture ) {return 'pretending to be a sub'}
|
| }
|
|
|
| my $thing = TypeCastSub.new;
|
| is($thing(), 'pretending to be a sub', 'overloaded () call works');
|
| is($thing.(), 'pretending to be a sub', 'overloaded .() call works');
|
| }
|
|
|
| {
|
| class TypeCastArray {
|
| method @.[ **@slice ] {return 'pretending to be an array'}
|
| }
|
|
|
| my $thing = TypeCastArray.new;
|
| is($thing[1], 'pretending to be an array', 'overloaded [] call works');
|
| is($thing[2,3], 'pretending to be an array', 'overloaded [] call works (slice)');
|
| is($thing.[4], 'pretending to be an array', 'overloaded .[] call works');
|
| is($thing.[5,6], 'pretending to be an array', 'overloaded .[] call works (slice)');
|
| }
|
|
|
| {
|
| class TypeCastHash {
|
| method %.{ **@slice } {return 'pretending to be a hash'}
|
| }
|
|
|
| my $thing = TypeCastHash.new;
|
| is($thing{'a'}, 'pretending to be a hash', 'overloaded {} call works');
|
| is($thing{'b','c'}, 'pretending to be a hash', 'overloaded {} call works (slice)');
|
| is($thing.{'d'}, 'pretending to be a hash', 'overloaded .{} call works');
|
| is($thing.{'e','f'}, 'pretending to be a hash', 'overloaded .{} call works (slice)');
|
| is($thing<a>, 'pretending to be a hash', 'overloaded <> call works');
|
| is($thing<b c>, 'pretending to be a hash', 'overloaded <> call works (slice)');
|
| is($thing.<d>, 'pretending to be a hash', 'overloaded .<> call works');
|
| is($thing.<e f>, 'pretending to be a hash', 'overloaded .<> call works (slice)');
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
The sigil-dot sequence in these short forms autogenerates the corresponding public operators, in exactly the same way that the sigil-dot in:
has $.action;
has @.sequence;
has %.mapping;
autogenerates public accessor methods.
And because it uses the same method-autogeneration mechanism, the specific sigil used to specify a short-form postcircumfix operator doesn't actually matter...as long as it's followed by a dot and the bracket pair containing the signature. (Though it's probably kinder to future readers of your code to stick with the "natural" sigil for each type of bracket.)
Note that the angle bracket subscripting form .<a b c> automatically translates itself into a call to .{'a','b','c'} , so defining methods for angles is basically useless.
The expected semantics of &.() is that of a type coercion which may or may not create a new object. So if you say:
$fido = Dog.new($spot)
it certainly creates a new Dog object. But if you say:
$fido = Dog($spot)
it might call Dog.new, or it might pull a Dog with Spot's identity from the dog cache, or it might do absolutely nothing if $spot already knows how to be a Dog. As a fallback, if no method responds to a coercion request, the class will be asked to attempt to do Dog.new($spot) instead.
It is also possible (and often preferable) to specify coercions from the other end, that is, for a class to specify how to coerce one of its values to some other class. If you define a method whose name is a declared type, it is taken as a coercion to that type:
From t/spec/S13-type-casting/methods.t lines 5–52 (no results): (skip)
| # L<S13/Type Casting/"whose name is a declared type, it is taken as a coercion
|
| # to that type">
|
|
|
| class CoercionTest {
|
| method Stringy { "foo" };
|
| method Numeric { 1.2 };
|
| }
|
|
|
| my $o = CoercionTest.new();
|
|
|
| #?rakudo skip 'Numeric, Stringy'
|
| is ~$o, 'foo', 'method Stringy takes care of correct stringification';
|
| #?rakudo skip 'Numeric, Stringy'
|
| ok +$o == 1.2, 'method Numeric takes care of correct numification';
|
|
|
| # RT #69378
|
| {
|
| class RT69378 {
|
| has $.x = 'working';
|
| method Str() { $.x }
|
| }
|
| is RT69378.new.Str, 'working', 'call to .Str works';
|
|
|
| class RT69378str is Str {
|
| has $.a = 'RT #69378';
|
| method Str() { $.a }
|
| }
|
| #?rakudo 2 todo 'RT 69378'
|
| is RT69378str.new.a, 'RT #69378', 'call to RT69378str.new properly initializes $.a';
|
| is RT69378str.new.Str, 'RT #69378', 'call to .Str works on "class is Str"';
|
| }
|
|
|
| is 1.Str.Str, "1", ".Str can be called on Str";
|
| is "hello".Str, "hello", ".Str can be called on Str";
|
|
|
| {
|
| # Not sure how to set the derived Str portion to a value, but that would be an
|
| # additional useful test here.
|
| class DerivedFromStr is Str {
|
| has $.a;
|
| }
|
| isa_ok DerivedFromStr.new, DerivedFromStr, 'DerivedFromStr.new isa DerivedFromStr';
|
| isa_ok DerivedFromStr.new, Str, 'DerivedFromStr.new isa DerivedFromStr';
|
| isa_ok DerivedFromStr.new.Str, DerivedFromStr, 'DerivedFromStr.new.Str isa DerivedFromStr';
|
| isa_ok DerivedFromStr.new.Str, Str, 'DerivedFromStr.new.Str isa Str';
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
method Str { self.makestringval() }
As with all methods, you can also export the corresponding multi:
method Str is export { self.makestringval() }
in which case you can use both calling forms:
$x.Str
Str($x)
If the source class and the destination class both specify a coercion routine, the ambiguity is settled by the ordinary rules of multiple dispatch. Usually the source class will have the more specific argument and will win; ties are also possible, and those calls will fail. Additional arguments may sway the dispatch one way or the other depending on the candidate list.
Note that, because the name of an anonymous class is unknown, coercion to an anonymous class can only be specified by the destination class:
$someclass = generate_class();
$someclass($x);
[ Top ]
[ Index of Synopses ]