This page was generated at 2010-03-10 06:03:31 GMT.
(syn r30019, pugs-smoke 19912)
[ Index of Synopses ]
Synopsis 12: Objects
Larry Wall <larry@wall.org>
Created: 27 Oct 2004
Last Modified: 7 Mar 2010
Version: 99
This synopsis summarizes Apocalypse 12, which discusses object-oriented programming.
From t/spec/S12-class/lexical.t lines 12–20 (no results): (skip)
| # L<S12/Classes>
|
|
|
| #?rakudo todo 'RT #61108'
|
| eval_lives_ok 'my class A {}', 'my class parses OK';
|
| #?rakudo todo 'RT #61108'
|
| eval_lives_ok '{ my class B {} } { my class B {} }',
|
| 'declare classes with the same name in two scopes.';
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
From t/spec/S12-class/basic.t lines 13–48 (no results): (skip)
| # L<S12/Classes>
|
| class Foo {}
|
|
|
| is Foo.perl, 'Foo', 'Classname.perl produces the class name';
|
|
|
| my $foo = Foo.new();
|
| ok($foo ~~ Foo, '... smartmatch our $foo to the Foo class');
|
|
|
| # note that S12 says that .isa() should be called on metaclasses.
|
| # However, making it an object .isa() means that classes are free to
|
| # override the behaviour without playing with the metamodel via traits
|
| ok($foo.isa(Foo), '.isa(Foo)');
|
| ok($foo.isa(::Foo), '.isa(::Foo)');
|
| ok($foo.isa("Foo"), '.isa("Foo")');
|
| ok(!$foo.isa("Bar"), '!.isa("Bar")');
|
|
|
| {
|
| my $foo_clone = $foo.clone();
|
| ok($foo_clone ~~ Foo, '... smartmatch our $foo_clone to the Foo class');
|
| }
|
|
|
| # Definedness of proto-objects and objects.
|
| ok(!Foo.defined, 'proto-objects are undefined');
|
| my Foo $ut1;
|
| ok(!$ut1.defined, 'proto-objects are undefined');
|
| ok(Foo.new.defined, 'instances of the object are defined');
|
|
|
| class Foo::Bar {}
|
|
|
| my $foo_bar = Foo::Bar.new();
|
| ok($foo_bar ~~ Foo::Bar, '... smartmatch our $foo_bar to the Foo::Bar class');
|
|
|
| ok($foo_bar.isa(Foo::Bar), '.isa(Foo::Bar)');
|
| ok(!$foo_bar.isa(::Foo), '!Foo::Bar.new.isa(::Foo)');
|
|
|
|
|
Highlighted:
small|full
A class is a module declared with the class keyword. As with modules, the public storage, interface, and name of the class is represented by a package and its name, which is usually (but not necessarily) a global name.
Taken as an object, a class represents all of the possible values of its type, and the class object can thus be used as a proxy for any "real" object of that type in calculating what a generic object of that type can do. The class object is an object, but it is not a Class, because there is no mandatory Class class in Perl 6. We wish to support both class-based and prototype-based OO programming. So all metaprogramming is done through the current object's HOW object, which can delegate metaprogramming to any metamodel it likes. However, by default, objects derived from Mu support a fairly standard class-based model.
There are two basic class declaration syntaxes:
class Foo; # rest of file is class definition
has $.foo;
class Bar { has $.bar } # block is class definition
The first form is allowed only as the first declaration in a compilation unit (that is, file or eval string).
If the class body begins with a statement whose main operator is a single prefix:<...> (yada) listop, the class name is introduced without a definition, and a second declaration of that class in the same scope does not complain about redefinition. (Statement modifiers are allowed on such a ... operator.) Thus you may forward-declare your classes:
class A {...} # introduce A as a class name without definition
class B {...} # introduce B as a class name without definition
my A $root .= new(:a(B));
class A {
has B $.a;
}
class B {
has A $.b;
}
As this example demonstrates, this allows for mutually recursive class definitions (though, of course, it can't allow recursive inheritance).
It is also possible to extend classes via the augment declarator, but that is considered somewhat antisocial and should not be used for forward declarations.
[Conjecture: we may also allow the proto and multi modifiers to explicitly declare classes with multiple bodies participating in a single definition intentionally.]
A named class declaration can occur as part of an expression, just like named subroutine declarations.
Classes are primarily for instance management, not code reuse. Consider using roles when you simply want to factor out common code.
Perl 6 supports multiple inheritance, anonymous classes, and autoboxing.
From t/spec/S12-class/anonymous.t lines 5–73 (no results): (skip)
| # L<S12/Classes/"PerlĀ 6 supports multiple inheritance, anonymous classes">
|
| plan 16;
|
|
|
| # Create and instantiate empty class; check .WHAT works and stringifies to
|
| # empty string.
|
| my $c1 = class { };
|
| my $t1 = $c1.new();
|
| ok(defined($t1), 'instantiated the class');
|
| ok($t1 ~~ $c1, 'isa check works');
|
| #?rakudo skip 'are anonymous classes required to be nameless?'
|
| is(~$c1.WHAT(), '', '.WHAT stringifies to empty string');
|
|
|
| # Anonymous classes with methods.
|
| my $c2 = class { method foo { 42 }; method bar { 28 } };
|
| my $t2 = $c2.new();
|
| is($t2.foo, 42, 'can call methods on anonymous classes');
|
| is($t2.bar, 28, 'can call methods on anonymous classes');
|
|
|
| # Anonymous classes with attributes.
|
| my $c3 = class { has $.x };
|
| my $t3 = $c3.new(x => 42);
|
| is($t3.x, 42, 'anonymous classes can have attributes');
|
|
|
| {
|
| my $class;
|
| lives_ok { $class = class { method meth() { return 42 } }} ,
|
| "anonymous class creation";
|
|
|
| my $a;
|
| ok ($a = $class.new), "instantiation of anonymous class";
|
| is $a.meth, 42, "calling a method on an instance of an anonymous class (1)";
|
|
|
| # And the same w/o using a $class variable:
|
| is (class { method meth() { return 42 } }).new.meth, 42,
|
| "calling a method on an instance of an anonymous class (2)";
|
| }
|
|
|
| # Anonymous classes can inherit from named classes.
|
| {
|
| class TestParent { method foo { 42 } }
|
| my $x = class :: is TestParent { }
|
| ok($x ~~ TestParent, 'anonymous class isa TestParent');
|
| is($x.foo, 42, 'inherited method from TestParent');
|
| }
|
|
|
| # RT #64888
|
| {
|
| sub rt64888 {
|
| (
|
| class {
|
| method Stringy() { 'RT #64888' }
|
| method Numeric() { 64888 }
|
| }
|
| ).new
|
| }
|
| my $i1;
|
| my $i2;
|
|
|
| lives_ok { $i1 = rt64888() }, 'can get anonymous class instance once';
|
| #?rakudo todo 'RT #64888'
|
| lives_ok { $i2 = rt64888() }, 'can get anonymous class instance twice';
|
|
|
| #?rakudo todo 'Numeric, Stringy'
|
| is ~$i1, 'RT #64888', 'anonymous class stringified works';
|
| #?rakudo skip 'Numeric, Stringy'
|
| is +$i1, 64888, 'anonymous class numified works';
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
All public method calls are "virtual" in the C++ sense. More surprisingly, any class name mentioned in a method is also considered virtual, that is, polymorphic on the actual type of the object.
You may derive from any built-in type, but the derivation of a low-level type like int may only add behaviors, not change the representation. Use composition and/or delegation to change the representation.
Since there are no barewords in Perl 6, bare class names must be predeclared. You can predeclare a stub class and fill it in later just as you would a subroutine.
From t/spec/S12-class/declaration-order.t lines 14–20 (no results): (skip)
| # L<S12/Classes/"bare class names must be predeclared">
|
|
|
| # need eval_lives_ok here because class declarations happen at compile time
|
| eval_lives_ok ' class A {}; class B is A {}; ', "base before derived: lives";
|
| eval_dies_ok ' class D is C {}; class C {}; ', "derived before base: dies";
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
You can force interpretation of a name as a class or type name using the :: prefix. In an rvalue context the :: prefix is a no-op, but in a declarational context, it binds a new type name within the declaration's scope along with anything else being declared by the declaration.
From t/spec/S12-class/literal.t lines 7–27 (no results): (skip)
| # L<S12/Classes/"class or type name using">
|
|
|
| # TODO: move that to t/spec/ as well
|
| BEGIN { @*INC.unshift('t/oo/class/TestFiles'); }
|
|
|
| # Testing class literals
|
| require Foo;
|
| my $test1;
|
|
|
| lives_ok {
|
| $test1 = ::Foo;
|
| }, "::Foo is a valid class literal";
|
|
|
| # Test removed per L<"http://www.nntp.perl.org/group/perl.perl6.language/22220">
|
| # Foo.isa(Class) is false.
|
| #isa_ok($test1, "Class", "It's a class");
|
|
|
| my $x = eval 'Foo';
|
| ok($x === ::Foo, "Foo is now a valid class literal");
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
Without a my or other scoping declarator, a bare class declarator declares an our declarator, that is, a name within the current package. Since class files begin parsing in the GLOBAL package, the first class declaration in the file installs itself as a global name, and subsequent declarations then install themselves into the current class rather than the global package.
Hence, to declare an inner class in the current package (or module, or class), use our class or just class. To declare a lexically scoped class, use my class. Class names are always searched for from innermost scopes to outermost. As with an initial ::, the presence of a :: within the name does not imply globalness (unlike in Perl 5). So the outward search can look in children of the searched namespaces.
Class traits are set using is:
class MyStruct is rw {...}
An "isa" is just a trait that happens to be another class:
From t/spec/S12-class/inheritance.t lines 7–144 (no results): (skip)
| # L<S12/Classes/An "isa" is just a trait that happens to be another class>
|
|
|
| class Foo {
|
| has $.bar is rw;
|
| has $.value is rw;
|
| method baz { return 'Foo::baz' }
|
| method getme($self:) returns Foo { return $self }
|
| }
|
|
|
| class Foo::Bar is Foo {
|
| has $.bar2 is rw;
|
| method baz { return 'Foo::Bar::baz' }
|
| method fud { return 'Foo::Bar::fud' }
|
| method super_baz ($self:) { return $self.Foo::baz() }
|
| }
|
|
|
| class Unrelated {
|
| method something { 'bad' };
|
| }
|
|
|
| my $foo_bar = Foo::Bar.new();
|
| isa_ok($foo_bar, Foo::Bar);
|
|
|
| ok(!defined($foo_bar.bar2()), '... we have our autogenerated accessor');
|
| ok(!defined($foo_bar.bar()), '... we inherited the superclass autogenerated accessor');
|
|
|
| lives_ok { $foo_bar.bar = 'BAR' }, '... our inherited the superclass autogenerated accessor is rw';
|
| is($foo_bar.bar(), 'BAR', '... our inherited the superclass autogenerated accessor is rw');
|
|
|
| lives_ok { $foo_bar.bar2 = 'BAR2'; }, '... our autogenerated accessor is rw';
|
|
|
| is($foo_bar.bar2(), 'BAR2', '... our autogenerated accessor is rw');
|
|
|
| is($foo_bar.baz(), 'Foo::Bar::baz', '... our subclass overrides the superclass method');
|
|
|
| is($foo_bar.super_baz(), 'Foo::baz', '... our subclass can still access the superclass method through Foo::');
|
| is($foo_bar.fud(), 'Foo::Bar::fud', '... sanity check on uninherited method');
|
|
|
| is($foo_bar.getme, $foo_bar, 'can call inherited methods');
|
| is($foo_bar.getme.baz, "Foo::Bar::baz", 'chained method dispatch on altered method');
|
|
|
| ok(!defined($foo_bar.value), 'value can be used for attribute name in derived classes');
|
| my $fud;
|
|
|
| lives_ok { $fud = $foo_bar.getme.fud }, 'chained method dispatch on altered method';
|
| is($fud, "Foo::Bar::fud", "returned value is correct");
|
|
|
| is $foo_bar.Foo::baz, 'Foo::baz', '$obj.Class::method syntax works';
|
| #?rakudo todo 'RT 69262'
|
| dies_ok { $foo_bar.Unrelated::something() },
|
| 'Cannot call unrelated method with $obj.Class::method syntax';
|
|
|
| # See thread "Quick OO .isa question" on p6l started by Ingo Blechschmidt:
|
| # L<"http://www.nntp.perl.org/group/perl.perl6.language/22220">
|
|
|
| # XXX are these still conforming to S12?
|
| ok Foo::Bar.isa(Foo), "subclass.isa(superclass) is true";
|
| ok Foo::Bar.isa(Foo::Bar), "subclass.isa(same_subclass) is true";
|
| #?pugs 2 todo "feature"
|
| #?rakudo skip 'does'
|
| ok Foo::Bar.does(Class), "subclass.does(Class) is true";
|
| #?rakudo 2 skip '::CLASS will give a Null PMC, which later explodes'
|
| ok !Foo::Bar.does(::CLASS), "subclass.does(CLASS) is false";
|
| ok !Foo::Bar.isa(::CLASS), "subclass.isa(CLASS) is false";
|
| #?rakudo 2 todo 'oo'
|
| ok !Foo::Bar.HOW.isa(Foo::Bar, Foo), "subclass.HOW.isa(superclass) is false";
|
| ok !Foo::Bar.HOW.isa(Foo::Bar, Foo::Bar), "subclass.HOW.isa(same_subclass) is false";
|
| #?pugs todo "bug"
|
| ok !Foo::Bar.HOW.isa(Foo::Bar, Class), "subclass.HOW.isa(Class) is false";
|
| ok !Foo::Bar.HOW.does(Foo::Bar, Class), "subclass.HOW.does(Class) is false";
|
| #?rakudo 2 skip 'no ::CLASS class'
|
| ok !Foo::Bar.HOW.isa(Foo::Bar, ::CLASS), "subclass.HOW.isa(CLASS) is false";
|
| #?pugs todo "feature"
|
| ok Foo::Bar.HOW.does(Foo::Bar, ::CLASS), "subclass.HOW.does(CLASS) is true";
|
|
|
| {
|
| my $test = '$obj.$meth is canonical (audreyt says)';
|
| class Abc {
|
| method foo () { "found" }
|
| }
|
| class Child is Abc { }
|
| is( eval('my $meth = "foo"; my $obj= Child.new; $obj."$meth"()'), 'found', $test);
|
| }
|
|
|
| # Erroneous dispatch found by TimToady++
|
|
|
| class X {
|
| method j () { 'X' }
|
| };
|
| class Z is X {}
|
| class Y is X {
|
| method k () { Z.new.j() }
|
| method j () { 'Y' }
|
| };
|
|
|
| is(Z.new.j(), 'X', 'inherited method dispatch works');
|
| is(Y.new.k(), 'X', 'inherited method dispatch works inside another class with same-named method');
|
|
|
| {
|
| class A {
|
| has @.x = <a b c>;
|
| has $.w = 9;
|
|
|
| method y($i) { return @.x[$i]; }
|
| }
|
|
|
| class B is A {
|
| has $.w = 10;
|
| method z($i) { return $.y($i); }
|
| }
|
|
|
| is( B.new.z(1), 'b', 'initializer carries through' );
|
| is( B.new.w, 10, 'initializer can be overriden by derived classes' );
|
| }
|
|
|
| # test that you can inherit from a class with :: in the name.
|
| {
|
| class A::B {
|
| method ab { 'a'; };
|
| };
|
|
|
| class A::B::C is A::B {
|
| method abc { 'b'; };
|
| }
|
| my $o = A::B::C.new;
|
|
|
| ok defined($o), 'can instantiate object from class A::B::C';
|
| is $o.ab, 'a', 'can access inherited method';
|
| is $o.abc, 'b', 'can access directly defined method';
|
| }
|
|
|
| # Make sure inheritnace from Mu works (got broken in Rakudo once).
|
| eval_lives_ok 'class NotAny is Mu { }; NotAny.new', 'inheritance from Mu works';
|
|
|
| #?rakudo todo 'trying to inherit from a non-existent class'
|
| eval_dies_ok 'class RT64642 is ::Nowhere {}', 'dies: class D is ::C {}';
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
From t/spec/S12-class/basic.t lines 49–120 (no results): (skip)
| # L<S12/Classes/An isa is just a trait that happens to be another class>
|
| class Bar is Foo {}
|
|
|
| ok(Bar ~~ Foo, '... smartmatch our Bar to the Foo class');
|
|
|
| my $bar = Bar.new();
|
| ok($bar ~~ Bar, '... smartmatch our $bar to the Bar class');
|
| ok($bar.isa(Bar), "... .isa(Bar)");
|
| ok($bar ~~ Foo, '... smartmatch our $bar to the Foo class');
|
| ok($bar.isa(Foo), "new Bar .isa(Foo)");
|
|
|
| {
|
| my $bar_clone = $bar.clone();
|
| ok($bar_clone ~~ Bar, '... smartmatch our $bar_clone to the Bar class');
|
| ok($bar_clone.isa(Bar), "... .isa(Bar)");
|
| ok($bar_clone ~~ Foo, '... smartmatch our $bar_clone to the Foo class');
|
| ok($bar_clone.isa(Foo), "... .isa(Foo)");
|
| }
|
|
|
| # Same, but with the "is Foo" declaration inlined
|
| #?rakudo skip 'not parsing is inside class yet'
|
| {
|
| class Baz { is Foo }
|
| ok(Baz ~~ Foo, '... smartmatch our Baz to the Foo class');
|
| my $baz = Baz.new();
|
| ok($baz ~~ Baz, '... smartmatch our $baz to the Baz class');
|
| ok($baz.isa(Baz), "... .isa(Baz)");
|
| }
|
|
|
| # test that lcfirst class names and ucfirst method names are allowed
|
|
|
| {
|
| class lowerCase {
|
| method UPPERcase {
|
| return 'works';
|
| }
|
| }
|
| is lowerCase.new.UPPERcase, 'works',
|
| 'type distinguishing is not done by case of first letter';
|
| }
|
|
|
| eval_dies_ok 'my $x; $x ~~ NonExistingClassName',
|
| 'die on non-existing class names';
|
|
|
| # you can declare classes over vivified namespaces, but not over other classes
|
|
|
| class One::Two::Three { } # auto-vivifies package One::Two
|
| class One::Two { }
|
| ok(One::Two.new, 'created One::Two after One::Two::Three');
|
| eval_dies_ok 'class One::Two { }', 'cannot redeclare an existing class';
|
| eval_lives_ok q[BEGIN {class Level1::Level2::Level3 {};}; class Level1::Level2 {};], 'RT 62898';
|
|
|
| class A61354_1 {
|
| ok !eval('method x { "OH HAI" }'), "can't just use eval to add method to class";
|
| };
|
|
|
| # RT #67784
|
| {
|
| class class {}
|
| #?rakudo skip 'RT #67784'
|
| isa_ok( class.new, 'class' );
|
| }
|
|
|
| # RT #64686
|
| eval_dies_ok 'class Romeo::Tango {}; Romeo::Juliet.rt64686',
|
| 'call to missing method in A::B dies after class A::C defined';
|
|
|
| # RT 72286
|
| eval_dies_ok 'class WritableSelf { method f { self = 5 } }; WritableSelf.new.f',
|
| 'self is not writable';
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
class Dog is Mammal {...}
MI is specified with multiple is modifiers:
class Dog is Mammal is Pet {...}
Roles use does instead of is:
class Dog is Mammal does Pet {...}
You may put these inside as well:
class Dog {
is Mammal;
does Pet;
...
}
Every object (including any class-based object) delegates to an instance of its metaclass. You can get at the metaclass of any object via the HOW method, which returns an instance of the metaclass. A "class" object is just considered an "empty" instance in Perl 6, more properly called a "prototype" or "generic" object, or just "type object". Perl 6 doesn't really have any classes named Class. Types of all kinds are instead named via these undefined type objects, which are considered to have exactly the same type as an instantiated version of themsleves. But such type objects are inert, and do not manage the state of class instances.
The actual object that manages instances is the metaclass object pointed to by the HOW syntax. So when you say "Dog", you're referring to both a package and a type object, the latter of which points to the object representing the class via HOW. The type object differs from an instance object not by having a different type but rather in the extent to which it is defined. Some objects may tell you that they are defined, while others may tell you that they are undefined. That's up to the object, and depends on how the metaclass chooses to dispatch the .defined method.
The notation ^Dog is syntactic sugar for Dog.HOW(), so ^ can be considered the "class" sigil when you want to talk about the current metaclass instance.
Classes are open and non-final by default, but may easily be closed or finalized not by themselves but by the entire application, provided nobody issued an explicit compile-time request that the class stay open or non-final. (Or a site policy could close any applications that use the policy.) Platforms that do dynamic loading of sub-applications probably don't want to close or finalize classes wholesale, however.
Roles take on some of the compile-time function of closed classes, so you should probably use those instead anyway.
A private class can be declared using my; most privacy issues are handled with lexical scoping in Perl 6. The fact that importation is lexical by default also means that any names your class imports are also private by default.
In an anonymous class declaration, :: by itself may represent the anonymous class name if desired:
class {...} # ok
class is Mammal {...} # WRONG
class :: is Mammal {...} # ok
class { is Mammal; ...} # also ok
Methods are routines declared in a class with the method keyword:
method doit ($a, $b, $c) { ... }
method doit ($self: $a, $b, $c) { ... }
method doit (MyName $self: $a, $b, $c) { ... }
method doit (::?CLASS $self: $a, $b, $c) { ... }
Declaration of the invocant is optional. You may always access the current invocant using the keyword self. You need not declare the invocant's type, since the lexical class of the invocant is known in any event because methods must be declared in the class of the invocant, though of course the actual (virtual) type may be a derived type of the lexical type. You could declare a more restrictive type, but that would probably be a bad thing for proper polymorphism. You may explicitly type the invocant with the lexical type, but any check for that will be optimized away. (The current lexically-determined class may always be named as ::?CLASS even in anonymous classes or roles.)
From t/spec/S12-attributes/recursive.t lines 46–91 (no results): (skip)
| #L<S12/Methods/current lexically-determined class ::?CLASS>
|
| #?rakudo skip '::?CLASS'
|
| {
|
| class C {
|
| has ::?CLASS $.attr is rw;
|
| };
|
|
|
| my C $a;
|
| my C $b;
|
| lives_ok {
|
| $a .= new();
|
| $b .= new(:attr($a));
|
| }, 'Can instantiate class with ::?CLASS attribute';
|
| is $b.attr, $a, '::?CLASS attribute stores correctly';
|
| lives_ok { $a.attr = $b; }, '::?CLASS cycles are fine';
|
| ok $b.attr.attr === $b, '::?CLASS cycles resolve correctly';
|
| lives_ok { $a.attr .= new(); }, 'Can instantiate attribute of type ::?CLASS';
|
| isa_ok $a.attr, C, '::?CLASS instantiates to correct class';
|
|
|
|
|
| class D is C { };
|
| my D $d;
|
| lives_ok {
|
| $d .= new();
|
| $d.attr .= new();
|
| }, 'Can instantiate derived class with ::?CLASS attribute';
|
| #?pugs todo 'bug'
|
| isa_ok $d.attr, C, '::?CLASS is lexical, not virtual';
|
| }
|
|
|
| # RT #67236
|
| {
|
| class Z {
|
| has Z @.as is rw;
|
| }
|
|
|
| my $good_a = Z.new;
|
| lives_ok { $good_a.as[0] = Z.new }, 'can assign';
|
| isa_ok $good_a.as[0], Z;
|
|
|
| my $bad_a = Z.new;
|
| lives_ok { $bad_a.as.push( Z.new ) }, 'can push';
|
| isa_ok $bad_a.as[0], Z;
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
To mark an explicit invocant, just put a colon after it:
method doit ($x: $a, $b, $c) { ... }
This is true also for multi methods:
multi method doit ($x: $a; $b; $c) { ... }
If you declare an explicit invocant for an Array type using an array variable, you may use that directly in list context to produce its elements
method push3 (@x: $a, $b, $c) { ... any(@x) ... }
Note that the self function is not context sensitive and thus always returns the current object as a single item even in list context. Hence if your current object happens to be an array but you did not declare it with an explicit array variable, you need to explicitly access the elements of the array somehow:
any(self) # WRONG
any(self[]) # okay
any(@(self)) # okay
any(@self) # WRONG unless you declared @self yourself
Private methods are declared using !:
From t/spec/S12-methods/private.t lines 6–43 (no results): (skip)
| # L<S12/Methods/"Private methods are declared using">
|
|
|
| class A {
|
| method !private {
|
| 12;
|
| }
|
| method public {
|
| self!private
|
| }
|
| }
|
|
|
| is A.new().public, 12, 'Can call private method from within the class';
|
|
|
| # indirect call syntax for public and private methods
|
|
|
| class Indir {
|
| method a {
|
| 'aa';
|
| }
|
| method !b {
|
| 'bb';
|
| }
|
| method b_acc1 {
|
| self!"b"();
|
| }
|
| method b_acc2 {
|
| self!'b'();
|
| }
|
| }
|
|
|
| my $o = Indir.new();
|
|
|
| is $o."a"(), "aa", 'indirect call to public method (double quotes)';
|
| is $o.'a'(), "aa", 'indirect call to public method (single quotes)';
|
| is $o.b_acc1, 'bb', 'indirect call to private method (double quotes)';
|
| is $o.b_acc2, 'bb', 'indirect call to private method (single quotes)';
|
| dies_ok {$o."b"() }, 'can not call private method via quotes from outside';
|
|
|
Highlighted:
small|full
method !think (Brain $self: $thought)
(Such methods are completely invisible to ordinary method calls, and are in fact called with a different syntax that uses ! in place of the . character. See below.)
Unlike with most other declarations, method declarations do not default to our semantics, or even my semantics, but rather has semantics. So instead of installing a symbol into a lexical or package symbol table, they merely install a public or private method in the current class or role via calls to its metaobject. (Likewise for submethod declarations--see "Submethods" below.)
Use of an explicit has declarator has no effect on the declaration. You may install additional aliases to the method in the lexical scope using my or in the current package using our. These aliases are named with &foo notation and return a Routine object that may be called as a subroutine, in which case you must supply the expected invocant as the first argument.
To call an ordinary method with ordinary method-dispatch semantics, use either the dot notation or indirect object notation:
From t/spec/S12-methods/instance.t lines 13–137 (no results): (skip)
| # L<S12/"Methods" /"either the dot notation or indirect object notation:">
|
| class Foo {
|
| method doit ($a, $b, $c) { $a + $b + $c }
|
| method noargs () { 42 }
|
| method nobrackets { 'mice' }
|
| method callsmethod1() { self.noargs(); }
|
| method callsmethod2 { self.noargs(); }
|
| }
|
|
|
| my $foo = Foo.new();
|
| is($foo.doit(1,2,3), 6, "dot method invocation");
|
|
|
| my $val;
|
| #?rakudo 2 skip 'indirect object notation'
|
| lives_ok { $val = doit $foo: 1,2,3; }, '... indirect method invocation works';
|
| is($val, 6, '... got the right value for indirect method invocation');
|
|
|
| is($foo.noargs, 42, "... no parentheses after method");
|
| is($foo.noargs(), 42, "... parentheses after method");
|
|
|
| {
|
| my $val;
|
| lives_ok { $val = $foo.noargs\ (); }, "... <unspace> + parentheses after method";
|
| is($val, 42, '... we got the value correctly');
|
| }
|
|
|
| {
|
| my $val;
|
| lives_ok { $val = $foo.nobrackets() }, 'method declared with no brackets';
|
| is($val, 'mice', '... we got the value correctly');
|
| }
|
|
|
| {
|
| my $val;
|
| lives_ok { $val = $foo.callsmethod1() }, 'method calling method';
|
| is($val, 42, '... we got the value correctly');
|
| };
|
|
|
| {
|
| my $val;
|
| lives_ok { $val = $foo.callsmethod2() }, 'method calling method with no brackets';
|
| is($val, 42, '... we got the value correctly');
|
| };
|
|
|
| {
|
| # This test could use peer review to make sure it complies with the spec.
|
| class Zoo {
|
| method a () { my %s; %s.b }
|
| method c () { my %s; b(%s) }
|
| method b () { 1 }
|
| }
|
| dies_ok( { Zoo.new.a }, "can't call current object methods on lexical data structures");
|
| dies_ok( { Zoo.new.c }, "meth(%h) is not a valid method call syntax");
|
| }
|
| # doesn't match, but defines "b"
|
| sub b() { die "oops" }
|
|
|
| # this used to be a Rakudo bug, RT #62046
|
| {
|
| class TestList {
|
| method list {
|
| 'method list';
|
| }
|
| }
|
| is TestList.new.list, 'method list', 'can call a method "list"';
|
| }
|
|
|
| # Test that methods allow additional named arguments
|
| # http://irclog.perlgeek.de/perl6/2009-01-28#i_870566
|
|
|
| {
|
| class MethodTester {
|
| method m ($x, :$y = '') {
|
| "$x|$y";
|
| }
|
| }
|
|
|
| my $obj = MethodTester.new;
|
|
|
| is $obj.m('a'), 'a|', 'basic sanity 1';
|
| is $obj.m('a', :y<b>), 'a|b', 'basic sanity 2';
|
| lives_ok { $obj.m('a', :y<b>, :z<b>) }, 'additional named args are ignored';
|
| is $obj.m('a', :y<b>, :z<b>), 'a|b', '... same, but test value';
|
|
|
| # and the same with class methods
|
|
|
| is MethodTester.m('a'), 'a|', 'basic sanity 1 (class method)';
|
| is MethodTester.m('a', :y<b>), 'a|b', 'basic sanity 2 (class method)';
|
| lives_ok { MethodTester.m('a', :y<b>, :z<b>) },
|
| 'additional named args are ignored (class method)';
|
| is MethodTester.m('a', :y<b>, :z<b>), 'a|b',
|
| '... same, but test value (class method)';
|
| }
|
|
|
| # test that public attributes don't interfere with private methods of the same
|
| # name (RT #61774)
|
|
|
| {
|
| class PrivVsAttr {
|
| has @something is rw;
|
| method doit {
|
| @something = <1 2 3>;
|
| self!something;
|
| }
|
| method !something {
|
| 'private method'
|
| }
|
| }
|
|
|
| my PrivVsAttr $a .= new;
|
| is $a.doit, 'private method',
|
| 'call to private method in presence of attribute';
|
| }
|
|
|
| # used to be RT #69206
|
|
|
| class AnonInvocant {
|
| method me(::T $:) {
|
| T;
|
| }
|
| }
|
|
|
| is AnonInvocant.new().me, AnonInvocant, 'a typed $: as invocant is OK';
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
$obj.doit(1,2,3)
doit $obj: 1,2,3
Indirect object notation now requires a colon after the invocant, even if there are no arguments after the colon:
From t/spec/S12-methods/indirect_notation.t lines 5–48 (no results): (skip)
| # L<S12/Methods/"Indirect object notation now requires a colon after the invocant, even if there are no arguments">
|
|
|
| plan 39;
|
|
|
|
|
| ##### Without arguments
|
| class T1
|
| {
|
| method a
|
| {
|
| 'test';
|
| }
|
| }
|
|
|
| {
|
| my T1 $o .= new;
|
| ok( "Still alive after new" );
|
|
|
| is( $o.a(), 'test', "The indirect object notation call without argument 1" );
|
| #?rakudo skip 'unimpl parse error near $o:'
|
| is( (a $o:), 'test', "The indirect object notation call without arguments 2" );
|
| }
|
|
|
| ##### With arguments
|
| class T2
|
| {
|
| method a( $x )
|
| {
|
| $x;
|
| }
|
| }
|
|
|
| {
|
| my T2 $o .= new;
|
| ok( "Still alive after new" );
|
| my $seed = 1000.rand;
|
| is( $o.a( $seed ), $seed, "The indirect object notation call with argument 1" );
|
| #?rakudo skip 'unimpl parse error near $o:'
|
| is( (a $o: $seed), $seed, "The indirect object notation call with arguments 2" );
|
| my $name = 'a';
|
| eval_dies_ok('$name $o: $seed', 'Indirect object notation and indirect method calls cannot be combined');
|
| }
|
|
|
|
|
Highlighted:
small|full
$handle.close;
close $handle:;
To reject method call and only consider subs, simply omit the colon from the invocation line:
close($handle);
close $handle;
However, here the built-in IO class defines method close () is export, which puts a multi sub close (IO) in scope by default. Thus if the $handle evaluates to an IO object, then the two subroutine calls above are still translated into method calls.
Dot notation can omit the invocant if it's in $_:
.doit(1,2,3)
Note that there is no corresponding notation for private methods.
!doit(1,2,3) # WRONG, would be parsed as not(doit(1,2,3))
self!doit(1,2,3) # okay
There are several forms of indirection for the method name. You can replace the identifier with a quoted string, and it will be evaluated as a quote and then the result of that is used as the method name.
From t/spec/S12-methods/indirect_notation.t lines 49–67 (no results): (skip)
| # L<S12/Methods/"There are several forms of indirection for the method name">
|
|
|
| {
|
| class A {
|
| method abc { 'abc' };
|
| method bcd { 'bcd' };
|
| }
|
| my $o = A.new();
|
|
|
| is $o."abc"(), 'abc', 'calling method with $object."methodname"';
|
| my $bc = 'bc';
|
| is $o."a$bc"(), 'abc', 'calling method with $object."method$name"';
|
| is $o."{$bc}d"(), 'bcd', 'calling method with $object."method$name"';
|
|
|
|
|
| my $meth = method { self.abc ~ self.bcd };
|
| is $o.$meth, 'abcbcd', 'calling method with $object.$methodref';
|
| }
|
|
|
Highlighted:
small|full
From t/spec/S12-methods/calling_syntax.t lines 34–48 (no results): (skip)
| # L<S12/Methods/"You can replace the identifier with a quoted string">
|
| eval_dies_ok(q{$x.'foo'}, 'indirect method call using quotes, no parens');
|
| is($x.'bar'(), 101, 'indirect method call using quotes, with parens');
|
| is($x.'identity'('qwerty'), 'qwerty', 'indirect method call using quotes, with parameter');
|
| {
|
| my $name = 'foo';
|
| eval_dies_ok(q{$x."$name"}, 'indirect method call, no parens');
|
| is($x."$name"(), 42, 'indirect method call, with parens');
|
| }
|
| {
|
| my $name = 'identity';
|
| is($x."$name"('asdf'), 'asdf', 'indirect method call, with parameter');
|
| }
|
|
|
| # vim: syn=perl6
|
Highlighted:
small|full
$obj."$methodname"(1,2,3) # use contents of $methodname as method name
$obj.'$methodname'(1,2,3) # no interpolation; call method with $ in name!
$obj!"$methodname"() # indirect call to private method name
As an aid to catching Perl 5 brainos, this quoted form always requires a parenthesized argument list to distinguish it from code that looks like a Perl 5 concatenation.
Within an interpolation, the double-quoted form may not contain whitespace. This does what the user expects in the common case of a quoted string ending with a period:
From t/spec/S02-literals/misc-interpolation.t lines 96–113 (no results): (skip)
| # L<S12/Methods/Within an interpolation, the double-quoted form>
|
| #?rakudo skip 'interpolation of indirect method calls'
|
| {
|
| class InterpolationTest {
|
| method f { 'int' }
|
| }
|
| my $x = InterpolationTest.new;
|
|
|
| # ORLY, STD.pm parses that as an indirect method call. It will warn,
|
| # but strictly speaking it's legal.
|
| is "|$x.'f'()|", '|int|',
|
| 'interpolation of indirect method calls (different quotes)';
|
| is "|$x."f"()|", '|int|',
|
| 'interpolation of indirect method calls (same quotes)';
|
| eval_dies_ok q["|$x."f "()"], '... but whitespaces are not allowed';
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
say "Foo = $foo.";
If you really want to call a method with whitespace, you may work around this restriction with a closure interpolation:
say "Foo = {$foo."a method"()}"; # OK
[Note: to help catch the mistaken use of infix:<.> as a string concatenation operator, Perl 6 will warn you about "useless use of quotes" at compile time if the string inside quotes is an identifier. (It does not warn about non-identifier strings, but such strings are likely to produce missing method errors at run time in any case.) Also, if there is whitespace around an intended . concatenation, it cannot be parsed as a method call at all; instead it fails at compile time because standard Perl 6 has a pseudo infix:<.> operator that always fails at compile time.]
For situations where you already have a method located, you can use a simple scalar variable in place of method name:
$methodobj = $foo ?? &bar !! &baz;
$obj.$methodobj(1,2,3)
or more succinctly but less readably:
$obj.$($foo ?? &bar !! &baz)(1,2,3)
The variable must contain a Callable object (usually of type Code), that is, a closure of some sort. Regardless of whether the closure was defined as a method or a sub or a block, the closure is called directly without any class dispatch; from the closure's point of view, however, it is always called as a method, with the object as its first argument, and the rest of the arguments second, third, and so on. For instance, such a closure may be used to abstract a "navigational" path through a data structure without specifying the root of the path till later:
$locator = -> $root, $x, $y { $root.<foo>[$x]<bar>{$y}[3] }
$obj.$locator(42,"baz") # $obj<foo>[42]<bar><baz>[3]
$locator = { .<here> }
$obj.$locator # $obj<here>
As a convenient form of documentation, such a closure may also be written in the form of an anonymous method:
$locator = method ($root: $x, $y) { $root.<foo>[$x]<bar>{$y}[3] }
$obj.$locator(42,"baz") # $obj<foo>[42]<bar><baz>[3]
$locator = method { self.<here> }
$obj.$locator # $obj<here>
Note however that, like any anonymous closure, an anonymous method can only be dispatched to directly, like a sub. You may, of course, bind an anonymous method to the name of a method in a class's public interface, in which case it is no longer anonymous, and may be dispatched to normally via the class. (And in fact, when the normal method dispatcher is calling individual candidates in its candidate list, it calls each candidate as a sub, not as a method, or you'd end up with recursive dispatchers.) But fundamentally, there's no such thing as a method closure. The method declarator on an anonymous method has the primary effect of making the declaration of the invocant optional. (It also makes it an official Routine that can be returned from, just as if you'd used sub to declare it.)
Instead of a scalar variable, an array variable may also be used:
$obj.@candidates(1,2,3)
From t/spec/S12-methods/indirect_notation.t lines 68–96 (no results): (skip)
| # L<S12/Methods/"$obj.@candidates(1,2,3)">
|
| {
|
| class T3 {
|
| has $.x;
|
| has $.y;
|
| has $.called is rw = 0;
|
|
|
| our method m1 () { $!called++; "$.x|$.y" };
|
| our method m2 () { $!called++; "$.x,$.y"; nextsame() };
|
| our method m3 () { $!called++; "$.x~$.y" };
|
| our method m4 () { $!called++; callsame(); };
|
| }
|
| my @c = (&T3::m1, &T3::m2, &T3::m3);
|
| my $o = T3.new(:x<p>, :y<q>);
|
|
|
| is $o.@c(), 'p|q', 'called the first candidate in the list, which did not defer';
|
| is $o.called, 1, 'called only one method dispatch';
|
|
|
| @c.shift();
|
| $o.called = 0;
|
| is $o.@c, 'p~q', 'got result from method we deferred to';
|
| is $o.called, 2, 'called total two methods during dispatch';
|
|
|
| @c.unshift(&T3::m4);
|
| $o.called = 0;
|
| is $o.@c, 'p~q', 'got result from method we deferred to, via call';
|
| is $o.called, 3, 'called total three methods during dispatch';
|
| }
|
|
|
Highlighted:
small|full
As with the scalar variant, string method names are not allowed, only Callable objects, The list is treated as a list of candidates to call. After the first successful call the rest of the candidates are discarded. Failure of the current candidate is indicated by calling nextwith or nextsame (see "Calling sets of methods" below).
Note also that the
$obj.$candidates(1,2,3)
form may dispatch to a list of candidates if $candidates is either a list or a special Code object representing a partial dispatch to a list of candidates. If $candidates (or any element of @candidates) is an iterable object it is expanded out recursively until Callable candidates are found. The call fails if it hits a candidate that is not Callable, Iterable, or List.
Another form of indirection relies on the fact that operators are named using a variant on hash subscript notation, which gives you these forms:
From t/spec/S12-methods/indirect_notation.t lines 97–145 (no results): (skip)
| # L<S12/Methods/"Another form of indirection relies on the fact">
|
| #?rakudo skip '$obj.infix:<+>'
|
| {
|
| is 1.infix:<+>(2), 3, 'Can call $obj.infix:<+>';
|
| my $op = '*';
|
| is 2.infix:{$op}(3), 6, 'can call $obj.infix:{$op}';
|
| is 2.infix:{'*'}(4), 8, 'can call $obj.infix:{"*"}';
|
| is 2.:<+>(7), 9, 'short form also works';
|
| my $x = 3;
|
| is $x.:<++>, 4, '.:<++> defaults to prefix';
|
| is $x, 4, '... and it changed the variable';
|
| }
|
|
|
| dies_ok { 23."nonexistingmethod"() }, "Can't call nonexisting method";
|
|
|
| {
|
| class T4 {
|
| has $.called = 0;
|
| our multi method m(Int $x) { $!called++; 'm-Int' }
|
| our multi method m(Num $x) { $!called++; 'm-Num' }
|
|
|
| our multi method n(Int $x) { $!called++; 'n-Int' }
|
| our multi method n(Num $x) { $!called++; 'n-Num' }
|
| }
|
|
|
| my $o = T4.new();
|
| my @cand-num = &T4::m, &T4::n;
|
| is ~$o.*@cand-num(3.4).sort, 'm-Num n-Num', '$o.*@cand(arg) (1)';
|
| is ~$o.*@cand-num(3).sort, 'm-Int m-Num n-Int n-Num', '$o.*@cand(arg) (2)';
|
| is $o.called, 6, 'right number of method calls';
|
| lives_ok { $o.*@cand-num() }, "it's ok with .* if no candidate matched (arity)";
|
| lives_ok { $o.*@cand-num([]) }, "it's ok with .* if no candidate matched (type)";
|
|
|
| $o = T4.new();
|
| is ~$o.+@cand-num(3.4).sort, 'm-Num n-Num', '$o.+@cand(arg) (1)';
|
| is ~$o.+@cand-num(3).sort, 'm-Int m-Num n-Int n-Num', '$o.+@cand(arg) (2)';
|
| is $o.called, 6, 'right number of method calls';
|
| dies_ok { $o.+@cand-num() }, "it's not ok with .+ if no candidate matched (arity)";
|
| dies_ok { $o.+@cand-num([]) }, "it's not ok with .+ if no candidate matched (type)";
|
|
|
| $o = T4.new();
|
| is ~$o.?@cand-num(3.4).sort, 'm-Num', '$o.?@cand(arg) (1)';
|
| is ~$o.?@cand-num(3).sort, 'm-Int', '$o.?@cand(arg) (2)';
|
| is $o.called, 2, 'right number of method calls';
|
| lives_ok { $o.?@cand-num() }, "it's ok with .? if no candidate matched (arity)";
|
| lives_ok { $o.?@cand-num([]) }, "it's ok with .? if no candidate matched (type)";
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
$x.infix:{$op}($y)
$x.prefix:{$op}
$x.postfix:{$op}
Generally you see these with the literal angle bracket form of subscript:
$a.infix:<*>($b) # equivalent to $a * $b
$a.prefix:<++> # equivalent to ++$a
$a.postfix:<++> # equivalent to $a++
If you omit the syntactic category, the call will be dispatched according to the number of arguments either as "prefix" or as "infix":
$a.:<+>($b) # equivalent to $a + $b
$a.:<++> # equivalent to ++$a
$a.:<!> # equivalent to !$a
@a.:<[*]> # equivalent to [*] @a
But it's probably better to spell out the syntactic category when the actual operator is not obvious:
$x.infix:{$op}($y)
$x.prefix:{$op}
You must use a special syntax to call a private method:
$mybrain!think($pinky)
self!think($pinky)
For a call on your own private method, you may also use the attribute-ish form:
From t/spec/S12-class/attributes.t lines 7–94 (no results): (skip)
| # L<S12/Methods/"For a call on your own private method"> |
| |
| class Counter { |
| has $!x; |
| method init { $!x = 41 } |
| method get { $!x } |
| method inc { $!x++ } |
| } |
| |
| my $c = Counter.new(); |
| try { |
| $c.x |
| } |
| ok($!, 'no public accessor for private attribute'); |
| $c.init(); |
| is($c.get(), 41, 'can assign and get from within the class'); |
| $c.inc(); |
| is($c.get(), 42, 'can auto-increment an attribute'); |
| |
| |
| class WithAlias { |
| has $x; |
| method set($a) { $x = $a } |
| method get { $!x } |
| } |
| |
| my $wa = WithAlias.new(); |
| $wa.set(99); |
| is($wa.get, 99, 'has with no twigil creates alias'); |
| |
| |
| class ManyTest { |
| has ($a, $b); |
| has ($.c, $.d); |
| has ($!e, $!f); |
| method t1 { |
| $a + $b |
| } |
| method t2 { |
| $!a + $!b |
| } |
| method t3 { |
| $!e + $!f |
| } |
| } |
| |
| my $m = ManyTest.new(a => 1, b => 2, c => 3, d => 4, e => 5, f => 6); |
| is($m.c, 3, 'list attribute declaration of publics works'); |
| is($m.d, 4, 'list attribute declaration of publics works'); |
| is($m.t1, 3, 'list attribute declaration of alias works'); |
| is($m.t2, 3, 'list attribute declaration of alias works'); |
| is($m.t3, 11, 'list attribute declaration of privates works'); |
| |
| |
| class Foo { |
| has %.bar is rw; |
| method set_bar { |
| %.bar<a> = 'baz'; |
| } |
| } |
| my $foo = Foo.new; |
| isa_ok($foo.bar.WHAT, Hash, 'hash attribute initialized'); |
| $foo.set_bar(); |
| is($foo.bar<a>, 'baz', 'hash attribute initialized/works'); |
| my %s = $foo.bar; |
| is(%s<a>, 'baz', 'hash attribute initialized/works'); |
| $foo.bar<b> = 'wob'; |
| is($foo.bar<b>, 'wob', 'hash attribute initialized/works'); |
| |
| class Bar { |
| has @.bar is rw; |
| method set_bar { |
| @.bar[0] = 100; |
| @.bar[1] = 200; |
| } |
| } |
| my $bar = Bar.new; |
| isa_ok($bar.bar.WHAT, Array, 'array attribute initialized'); |
| $bar.set_bar(); |
| is($bar.bar[0], 100, 'array attribute initialized/works'); |
| is($bar.bar[1], 200, 'array attribute initialized/works'); |
| my @t = $bar.bar; |
| is(@t[0], 100, 'array attribute initialized/works'); |
| is(@t[1], 200, 'array attribute initialized/works'); |
| $bar.bar[2] = 300; |
| is($bar.bar[2], 300, 'array attribute initialized/works'); |
| |
| # vim: ft=perl6 |
Highlighted:
small|full
$!think($pinky) # short for $(self!think($pinky))
Parentheses (or a colon) are required on the dot/bang notations if there are any arguments (not counting adverbial arguments). There may be no space between the method name and the left parenthesis unless you make use of "unspace":
From t/spec/S12-methods/syntax.t lines 6–28 (no results): (skip)
| # L<S12/Methods/"no space between the method name and the left parenthesis">
|
|
|
| class A {
|
| multi method doit () { 'empty' };
|
| multi method doit ($a, $b, *@rest) {
|
| "a:$a|b:{$b}!" ~ @rest.join('!');
|
| }
|
| }
|
|
|
| $_ = A.new();
|
|
|
| is .doit, 'empty', 'plain call with no args';
|
| is .doit(), 'empty', 'method call with parens and no args';
|
| eval_dies_ok '.doit ()', '.doit () illegal';
|
| is .doit\ (), 'empty', 'method call with unspace';
|
|
|
| is (.doit: 1, 2, 3), 'a:1|b:2!3', 'list op with colon';
|
| is (.doit: 1, 2, 3, 4), 'a:1|b:2!3!4', 'list op with colon, slurpy';
|
| #?rakudo 3 skip 'switch-from-paren-to-listop form'
|
| is (.doit(1): 2, 3), 'a:1|b:2!3', 'list op with colon';
|
| is (.doit(1, 2): 3), 'a:1|b:2!3', 'list op with colon';
|
| is (.doit\ (1, 2): 3), 'a:1|b:2!3', 'list op with colon, unspace';
|
|
|
Highlighted:
small|full
.doit # okay, no arguments
.doit() # okay, no arguments
.doit () # ILLEGAL (two terms in a row)
.doit\ () # okay, no arguments, same as .doit() (unspace form)
Note that the named method call forms are special and do not use the dot form of postfix. If you attempt to use the postfix operator form, it will assume you want to call the method with no arguments and then call the result of that:
.doit.() # okay, no arguments *twice*, same as .doit().()
.doit\ .() # okay, no arguments *twice*, same as .doit.().() (unspace form)
However, you can turn any of the named forms above into a list operator by appending a colon:
.doit: 1,2,3 # okay, three arguments
.doit(1): 2,3 # okay, one argument plus list
.doit (): 1,2,3 # ILLEGAL (two terms in a row)
In particular, this allows us to pass a closure in addition to the "normal" arguments:
.doit: { $^a <=> $^b } # okay
.doit(): { $^a <=> $^b } # okay
.doit(1,2,3): { $^a <=> $^b } # okay
In case of ambiguity between indirect object notation and dot form, the nearest thing wins:
dothis $obj.dothat: 1,2,3
means
dothis ($obj.dothat(1,2,3))
and you must say
dothis ($obj.dothat): 1,2,3
or
$obj.dothat.dothis: 1,2,3
if you mean the other thing.
Also note that if any term in a list is a bare closure or pointy sub, it will be considered to be the final argument of its list unless the closure's right curly is followed immediately by comma or colon. In particular, a method call does *not* extend the list, so you can say:
From t/spec/S12-methods/syntax.t lines 29–44 (no results): (skip)
| # L<S12/Methods/"if any term in a list is a bare closure">
|
| #?rakudo skip 'adverbial closures'
|
| is (1..8).grep: { $_ % 2 }.map: { $_ - 1}.join('|'), '0|2|4|6',
|
| 'adverbial closure has right precedence and associativity';
|
|
|
| # Used to be Rakudo RT #61988, $.foo form didn't accept arguments
|
|
|
| class B {
|
| method a ($a, $b) { $a + $b }
|
| method b { $.a(2, 3) }
|
| }
|
|
|
| is B.new.b, 5, '$.a can accept arguments';
|
|
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
@list.grep: { $_ % 2 }.map: { $_ - 1 }.say
and that will be taken as equivalent to
@list.grep({ $_ % 2 }).map({ $_ - 1 }).say
Methods (and subs) may be declared as lvalues with is rw. You can use an argumentless rw method anywhere you can use a variable, including in temp and let statements. (In fact, you can use an rw method with arguments as a variable as long as the arguments are used only to identify the actual value to change, and don't otherwise have strange side effects that differ between rvalue and lvalue usage. Setter methods that expect the new value as an argument do not fall into the well-behaved category, however.)
From t/spec/S12-methods/lvalue.t lines 6–46 (no results): (skip)
| # L<S12/Methods/may be declared as lvalues with is rw.>
|
|
|
| class T {
|
| has $.a;
|
| has $.b;
|
| method l1 is rw {
|
| return $.a;
|
| }
|
|
|
| method l2 is rw {
|
| $.b;
|
| }
|
| }
|
|
|
| my $o = T.new(:a<x>, :b<y>);
|
|
|
| is $o.l1, 'x', 'lvalue method as rvalue with explicit return';
|
| is $o.l2, 'y', 'lvalue method as rvalue with implicit return';
|
|
|
| lives_ok { $o.l1 = 3 }, 'can assign to the lvalue method (explicit return)';
|
| is $o.l1, 3, '... and the assignment worked';
|
| is $o.a, 3, '...also to the attribute';
|
|
|
| lives_ok { $o.l2 = 4 }, 'can assign to the lvalue method (implicit return)';
|
| is $o.l2, 4, '... and the assignment worked';
|
| is $o.b, 4, '...also to the attribute';
|
|
|
| my ($a, $b);
|
| lives_ok { temp $o.l1 = 8; $a = $o.a },
|
| 'can use lvalue method in temp() statement (explicit return)';
|
| is $o.l1, 3, '... and the value was reset';
|
| is $o.a, 3, '... also on the attribute';
|
| is $a, 8, 'but the temp assignment had worked';
|
|
|
| lives_ok { temp $o.l2 = 9; $b = $o.b },
|
| 'can use lvalue method in temp() statement (explicit return)';
|
| is $o.l2, 4, '... and the value was reset';
|
| is $o.b, 3, '... also on the attribute';
|
| is $a, 9, 'but the temp assignment had worked';
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
Method calls on mutable scalars always go to the object contained in the scalar (autoboxing value types as necessary):
$result = $object.doit();
$length = "mystring".codes;
Method calls on non-scalar variables just calls the Array, Hash or Code object bound to the variable:
$elems = @array.elems;
@keys = %hash.keys;
$sig = &sub.signature;
Use the prefix VAR macro on a scalar variable to get at its underlying Scalar object:
if VAR($scalar).readonly {...}
VAR is a no-op on a non-scalar variables and values:
VAR(1); # 1
VAR(@x); # @x
There's also a corresponding postfix:<.VAR> macro that can be used as if it were a method:
if $scalar.VAR.readonly {...}
(But since it's a macro, VAR is not dispatched as a real method. To dispatch to a real .VAR method, use the indirect $obj."VAR" form.)
You can also get at the container through the appropriate symbol table:
if MY::<$scalar>.readonly {...}
From t/spec/S12-class/inheritance-class-methods.t lines 6–21 (no results): (skip)
| # L<S12/Class methods/>
|
|
|
| class C {method h {42}}
|
| class B is C { method g { self.f } };
|
| class A is B { method f {1; } };
|
|
|
| class AA {method i {108}}
|
| class D is A is AA {method f {2} }
|
|
|
| is(A.g(), 1, 'inheritance works on class methods');
|
| is(A.h(), 42, '>1 level deep inheritance works on class methods');
|
| is(D.h(), 42, 'multiple inheritance works on class methods (1)');
|
| is(D.i(), 108, 'multiple inheritance works on class methods (2)');
|
| is(D.f(), 2, 'method from class is selected over inherited method');
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
Other OO languages give you the ability to declare "class" methods that either don't need or actively prohibit calls on instances. Perl 6 gives you a choice. If you declare an ordinary method, it can function as a "class" method when you pass it a type object such as "Dog" regardless of how defined the prototype object is, as long as the method body doesn't try to access any information that is undefined in the current instance.
Alternately, you can associate a method with the current metaclass instance, which as a singleton object knows your package, and can function as a more traditional "class" method:
From t/spec/S12-attributes/class.t lines 55–115 (no results): (skip)
| # L<S12/Class methods/"you can associate a method with the current
|
| # metaclass instance">
|
|
|
| {
|
| class T1 {
|
| our $c = 0;
|
| method ^count($obj) {
|
| return $c;
|
| }
|
| method mi { ++$c };
|
| method md { --$c };
|
| }
|
|
|
| my ($a, $b, $c) = map { T1.new() }, 1..3;
|
| is $c.mi, 1, 'can increment class variable in instance method';
|
| is $b.mi, 2, '.. and the class variable is really shared';
|
| is $a.count, 2, 'can call the class method on an object';
|
| is T1.count, 2, '... and on the proto object';
|
| is T1.^count, 2, '... and on the proto object with Class.^method';
|
| is $a.^count, 2, '... and $obj.^method';
|
| is T1.HOW.count(T1), 2, '... and by explicitly using .HOW with proto object';
|
| is $a.HOW.count($a), 2, '... and by explicitly using .HOW with instance';
|
|
|
| }
|
|
|
| {
|
| class Oof {
|
| my $.x is rw;
|
| }
|
| my $x = Oof.new();
|
| $x.x = 42;
|
| is($x.x, 42, "class attribute accessors work");
|
| my $y = Oof.new();
|
| is($y.x, 42, "class attributes shared by all instances");
|
| }
|
|
|
| # RT #57336
|
| {
|
| # TODO: Test that the exceptions thrown here are the right ones
|
| # and not the result of some other bug.
|
|
|
| my $bad_code;
|
|
|
| $bad_code = '$.a';
|
| eval $bad_code;
|
| ok $! ~~ Exception, "bad code: '$bad_code'";
|
|
|
| $bad_code ='$!a';
|
| eval $bad_code;
|
| ok $! ~~ Exception, "bad code: '$bad_code'";
|
|
|
| $bad_code = 'class B0rk { has $.a; say $.a; }';
|
| eval $bad_code;
|
| ok $! ~~ Exception, "bad code: '$bad_code'";
|
|
|
| $bad_code = 'class Chef { my $.a; say $.a; }';
|
| eval $bad_code;
|
| ok $! ~~ Exception, "bad code: '$bad_code'";
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
our $count;
method ^count { return $count }
Such a metaclass method is always delegated to the HOW object just as methods like .does are, so it's possible to call this as Dog.count or $dog.count. However, best practice is probably to call such a class method as Dog.^count or $dog.^count to make it clear that it's in its own namespace separate from ordinary methods, and so that your class method cannot be accidentally overridden by an ordinary method in a subclass--presuming you don't want to allow for that possibility.
From t/spec/S12-attributes/class.t lines 12–54 (no results): (skip)
| #L<S12/Class methods/metaclass method always delegated>
|
|
|
| plan 28;
|
|
|
| class Foo {
|
| our $.bar = 23;
|
| our $.yada is rw = 13;
|
| }
|
|
|
| my $test = 0;
|
| ok ($test = Foo.bar), 'accessors for class attributes work';
|
| is $test, 23, 'class attributes really work';
|
|
|
| class Baz is Foo {};
|
|
|
| my $test2 = 0;
|
| lives_ok { $test2 = Baz.bar }, 'inherited class attribute accessors work';
|
| is $test2, 23, 'inherited class attributes really work';
|
|
|
| my $test3 = 0;
|
| lives_ok { Baz.yada = 42; $test3 = Baz.yada }, 'inherited rw class attribute accessors work';
|
| is $test3, 42, 'inherited rw class attributes really work';
|
|
|
| class Quux is Foo { has $.bar = 17; };
|
|
|
| my $test4 = 0;
|
| #?pugs 99 todo 'class attributes'
|
| lives_ok { $test4 = Quux.new() },
|
| 'Can instantiate with overridden instance method';
|
| is $test4.bar, 17, 'Instance call gets instance attribute, not class attribute';
|
| my $test5 = 0;
|
| dies_ok {$test5 = Quux.bar}, 'class attribute accessor hidden by accessor in subclass; we do not magically ignore it';
|
| #?rakudo 5 todo 'class attributes'
|
| is $test5, 23, 'class attribute really works, even when overridden';
|
| my $test6 = 0;
|
| lives_ok { $test6 = Quux.^bar}, 'class attribute accessible via ^name';
|
| is $test6, 23, 'class attribute via ^name really works';
|
| my $test7 = 0;
|
| #?pugs 2 todo 'feature'
|
| lives_ok {$test7 = $test4.^bar},
|
| 'class attribute accessible via ^name called on instance';
|
| is $test7, 23, 'class attribute via $obj.^name really works';
|
|
|
Highlighted:
small|full
From t/spec/S12-methods/submethods.t lines 12–125 (no results): (skip)
| # L<S12/Submethods>
|
| {
|
|
|
| lives_ok {
|
| class Foo { has $.foo_build; submethod BUILD() { $!foo_build++ } }
|
| class Bar is Foo { has $.bar_build; submethod BUILD() { $!bar_build++ } }
|
| }, "class definitions were parsed/run/compiled";
|
|
|
| my $a;
|
| lives_ok {$a = Foo.new()}, "Foo.new() worked (1)";
|
| is $a.foo_build, 1, "Foo's BUILD was called";
|
| # is instead of todo_is to avoid unexpected succeedings
|
| dies_ok { $a.bar_build }, "Bar's BUILD counter not available";
|
|
|
| my $b;
|
| lives_ok {$b = Bar.new()}, "Bar.new() worked";
|
| is $b.foo_build, 1, "Foo's BUILD was called again";
|
| is $b.bar_build, 1, "Bar's BUILD was called, too";
|
|
|
| # The next three tests are basically exactly the same as the first three tests
|
| # (not counting the initial class definition). This is to verify our call to
|
| # Bar.new didn't removed/changed some internal structures which'd prevent
|
| # Foo.BUILD of getting called.
|
| my $c;
|
| lives_ok {$c = Foo.new()}, "Foo.new() worked (2)";
|
| is $c.foo_build, 1, "Foo's BUILD was called again";
|
| }
|
|
|
| # See thread "BUILD and other submethods" on p6l
|
| # L<"http://groups-beta.google.com/group/perl.perl6.language/msg/e9174e5538ded4a3">
|
| {
|
| class Baz {
|
| has $.baz_blarb = 0;
|
| has $.grtz_blarb = 0;
|
| submethod blarb() { $!baz_blarb++ }
|
| }
|
| class Grtz is Baz {
|
| submethod blarb() { $!grtz_blarb++ }
|
| }
|
|
|
| my ($baz, $grtz);
|
| lives_ok {$baz = Baz.new}, "Baz.new() worked";
|
| lives_ok {$grtz = Grtz.new}, "Grtz.new() worked";
|
|
|
| lives_ok { $baz.blarb }, 'can call submethod on parent class';
|
| is $baz.baz_blarb, 1, "Baz's submethod blarb was called";
|
| is $baz.grtz_blarb, 0, "Grtz's submethod blarb was not called";
|
|
|
| lives_ok { $grtz.blarb }, 'can call submethod on child class';
|
| is $grtz.baz_blarb, 0, "Baz's submethod blarb was not called";
|
| is $grtz.grtz_blarb, 1, "Grtz's submethod blarb was called now";
|
|
|
| lives_ok { $grtz.Baz::blarb }, '$obj.Class::submthod';
|
| is $grtz.baz_blarb, 1, "Baz's submethod blarb was called now";
|
| is $grtz.grtz_blarb, 1, "Grtz's submethod blarb was not called again";
|
| }
|
|
|
| # Roles with BUILD
|
| # See thread "Roles and BUILD" on p6l
|
| # L<"http://www.nntp.perl.org/group/perl.perl6.language/21277">
|
| #?rakudo skip 'outer lexicals in roles'
|
| {
|
| my $was_in_a1_build = 0;
|
| my $was_in_a2_build = 0;
|
| role RoleA1 { multi submethod BUILD() { $was_in_a1_build++ } }
|
| role RoleA2 { multi submethod BUILD() { $was_in_a2_build++ } }
|
| class ClassA does RoleA1 does RoleA2 {}
|
|
|
| ClassA.new;
|
|
|
| is $was_in_a1_build, 1, "roles' BUILD submethods were called when mixed in a class (1)";
|
| is $was_in_a2_build, 1, "roles' BUILD submethods were called when mixed in a class (2)";
|
| }
|
|
|
| #?rakudo skip 'roles and submethods'
|
| {
|
| my $was_in_b1_build = 0;
|
| my $was_in_b2_build = 0;
|
| role RoleB1 { multi submethod BUILD() { $was_in_b1_build++ } }
|
| role RoleB2 { multi submethod BUILD() { $was_in_b2_build++ } }
|
| class ClassB {}
|
|
|
| my $B = ClassB.new;
|
| is $was_in_b1_build, 0, "roles' BUILD submethods were not yet called (1)";
|
| is $was_in_b2_build, 0, "roles' BUILD submethods were not yet called (2)";
|
|
|
| $B does (RoleB1, RoleB2);
|
| #?pugs 2 todo 'feature'
|
| is $was_in_b1_build, 1, "roles' BUILD submethods were called now (1)";
|
| is $was_in_b2_build, 1, "roles' BUILD submethods were called now (2)";
|
| };
|
|
|
| # BUILD with signatures that don't map directly to attributes
|
| #?rakudo skip 'BUILD'
|
| {
|
| class ClassC
|
| {
|
| has $.double_value;
|
|
|
| submethod BUILD ( $value = 1 )
|
| {
|
| $.double_value = $value * 2;
|
| }
|
| }
|
|
|
| my $C = ClassC.new();
|
| is( $C.double_value, 2,
|
| 'BUILD() should allow default values of optional params in signature' );
|
|
|
| my $C2 = ClassC.new( :value(100) );
|
| is( $C2.double_value, 200, '... or value passed in' );
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
From t/spec/S14-roles/submethods.t lines 12–31 (no results): (skip)
| # L<S12/Submethods>
|
|
|
| =end pod
|
|
|
|
|
| role AddBuild
|
| {
|
| has $.did_build = 0;
|
| submethod BUILD ( $self: )
|
| {
|
| $!did_build = 1;
|
| }
|
| }
|
|
|
| class MyClass does AddBuild {}
|
|
|
| my $class = MyClass.new();
|
| ok( $class.did_build, 'Class that does role should do submethods of role' );
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
Submethods are for declaring infrastructural methods that shouldn't be inherited by subclasses, such as initializers:
submethod BUILD ($arg) {
$.attr = $arg;
}
Apart from the keyword, submethod declaration and call syntax is identical to method syntax. You may mix methods and submethods of the same name within the class hierarchy, but only the methods are visible to derived classes via inheritance. A submethod is called only when a method call is dispatched directly to the current class.
Conjecture: in order to catch spelling errors it is a compile-time warning to define a submethod in any class that does not inherit the corresponding method name from some base class. More importantly, this would help safeguard Liskov substitutability. (But note that the standard Mu class already supplies a default BUILD and new.)
From t/spec/S12-attributes/augment-and-initialization.t lines 7–50 (no results): (skip)
| # L<S12/"Attributes">
|
|
|
| plan 8;
|
|
|
| diag('Test for class attribute initialization');
|
|
|
|
|
| {
|
| class T1 { }
|
| class T2 { }
|
| eval_lives_ok 'augment class T1 { has $.t = 1 }; 1',
|
| "Try to initialize public attribute";
|
|
|
| eval_lives_ok q'
|
| augment class T2 {
|
| has $!t = 2;
|
| method get { $!t };
|
| }; 1 }',
|
| "Try to initialize private attribute";
|
|
|
|
|
| my T1 $o1;
|
| my T2 $o2;
|
|
|
| $o1 = T1.new();
|
| $o2 = T2.new();
|
| is $o1.t, 1,
|
| "Testing value for initialized public attribute.";
|
| dies_ok { $o2.t },
|
| "Try to access the initialized private attribute.";
|
| is try { $o2.get }, 2,
|
| "Testing value for initialized private attribue.";
|
|
|
| $o1 = T1.new( t => 3 );
|
| $o2 = T2.new( t => 4 );
|
| is $o1.t, 3,
|
| "Testing value for attributes which is initialized by constructor.";
|
| dies_ok { $o2.t },
|
| "Try to access the private attribute which is initialized by constructor.";
|
| is try { $o2.get }, 4,
|
| "Testing value for private attribue which is initialized by constructor.";
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
From t/spec/S12-attributes/recursive.t lines 13–30 (no results): (skip)
| #L<S12/Attributes>
|
| {
|
| class A {
|
| has A $.attr is rw;
|
| };
|
|
|
| my A $a;
|
| my A $b;
|
| lives_ok {
|
| $a .= new();
|
| $b .= new(:attr($a));
|
| }, 'Can instantiate class with recursively-typed attribute';
|
| isa_ok $a, 'A', 'Sanity check, $a is of type A';
|
| ok $b.attr === $a, "Recursively-typed attribute stores correctly";
|
| lives_ok { $a.attr = $b; }, "Cycles are fine";
|
| ok $b.attr.attr === $b, "Cycles resolve correctly";
|
| }
|
|
|
Highlighted:
small|full
From t/spec/S12-attributes/instance.t lines 88–126 (no results): (skip)
| # L<S12/Attributes>
|
|
|
|
|
| {
|
| class Foo5 {
|
| has $.tail is rw;
|
| has @.legs;
|
| has $!brain;
|
|
|
| method set_legs (*@legs) { @.legs = @legs }
|
| method inc_brain () { $!brain++ }
|
| method get_brain () { $!brain }
|
| };
|
| my $foo = Foo5.new();
|
| ok($foo ~~ Foo5, '... our Foo5 instance was created');
|
|
|
| lives_ok {
|
| $foo.tail = "a";
|
| }, "setting a public rw attribute";
|
| is($foo.tail, "a", "getting a public rw attribute");
|
|
|
| #?rakudo 2 todo 'oo'
|
| lives_ok { $foo.set_legs(1,2,3) }, "setting a public ro attribute (1)";
|
| is($foo.legs.[1], 2, "getting a public ro attribute (1)");
|
|
|
| dies_ok {
|
| $foo.legs = (4,5,6);
|
| }, "setting a public ro attribute (2)";
|
| #?rakudo todo 'oo'
|
| is($foo.legs.[1], 2, "getting a public ro attribute (2)");
|
|
|
| lives_ok { $foo.inc_brain(); }, "modifiying a private attribute (1)";
|
| is($foo.get_brain, 1, "getting a private attribute (1)");
|
| lives_ok {
|
| $foo.inc_brain();
|
| }, "modifiying a private attribute (2)";
|
| is($foo.get_brain, 2, "getting a private attribute (2)");
|
| }
|
|
|
Highlighted:
small|full
From t/spec/S12-attributes/instance.t lines 189–562 (no results): (skip)
| # L<S12/Attributes>
|
| ok eval('class Foo7e { has $.attr = 42 }; 1'), "class definition worked";
|
| is eval('Foo7e.new.attr'), 42, "default attribute value (1)";
|
|
|
| {
|
| my $was_in_supplier = 0;
|
| sub forty_two_supplier() { $was_in_supplier++; 42 }
|
| ok eval('class Foo10e { has $.attr = forty_two_supplier() }; 1'),
|
| 'class definition using "= {...}" worked';
|
| is eval('Foo10e.new.attr'), 42, "default attribute value (4)";
|
| is $was_in_supplier, 1, "forty_two_supplier() was actually executed";
|
| eval('Foo10e.new');
|
| is $was_in_supplier, 2, "forty_two_supplier() is executed per instantiation";
|
| }
|
|
|
| # check that doing something in submethod BUILD works
|
| {
|
| class Foo7 {
|
| has $.bar is rw;
|
| has $.baz;
|
|
|
| submethod BUILD ($.bar = 5, $baz = 10 ) {
|
| $!baz = 2 * $baz;
|
| }
|
| }
|
|
|
| my $foo7 = Foo7.new();
|
| is( $foo7.bar, 5,
|
| 'optional attribute should take default value without passed-in value' );
|
| is( $foo7.baz, 20,
|
| '... optional non-attribute should too' );
|
| $foo7 = Foo7.new( :bar(4), :baz(5) );
|
| is( $foo7.bar, 4,
|
| 'optional attribute should take passed-in value over default' );
|
| is( $foo7.baz, 10,
|
| '... optional non-attribute should too' );
|
| }
|
|
|
|
|
| # check that args are passed to BUILD
|
| {
|
| class Foo8 {
|
| has $.a;
|
| has $.b;
|
|
|
| submethod BUILD(:$foo, :$bar) {
|
| $!a = $foo;
|
| $!b = $bar;
|
| }
|
| }
|
|
|
| my $foo = Foo8.new(foo => 'c', bar => 'd');
|
| ok($foo.isa(Foo8), '... our Foo8 instance was created');
|
|
|
| is($foo.a, 'c', 'BUILD received $foo');
|
| is($foo.b, 'd', 'BUILD received $bar');
|
| }
|
|
|
| # check mixture of positional/named args to BUILD
|
|
|
| {
|
| class Foo9 {
|
| has $.a;
|
| has $.b;
|
|
|
| submethod BUILD($foo, :$bar) {
|
| $.a = $foo;
|
| $.b = $bar;
|
| }
|
| }
|
|
|
| dies_ok({ Foo9.new('pos', bar => 'd') }, 'cannot pass positional to .new');
|
| }
|
|
|
| # check $self is passed to BUILD
|
| {
|
| class Foo10 {
|
| has $.a;
|
| has $.b;
|
| has $.c;
|
|
|
| submethod BUILD($self: :$foo, :$bar) {
|
| $!a = $foo;
|
| $!b = $bar;
|
| $!c = 'y' if $self.isa(Foo10);
|
| }
|
| }
|
|
|
| {
|
| my $foo = Foo10.new(foo => 'c', bar => 'd');
|
| ok($foo.isa(Foo10), '... our Foo10 instance was created');
|
|
|
| is($foo.a, 'c', 'BUILD received $foo');
|
| is($foo.b, 'd', 'BUILD received $bar');
|
| is($foo.c, 'y', 'BUILD received $self');
|
| }
|
| }
|
|
|
| {
|
| class WHAT_ref { };
|
| class WHAT_test {
|
| has WHAT_ref $.a;
|
| has WHAT_test $.b is rw;
|
| }
|
| my $o = WHAT_test.new(a => WHAT_ref.new(), b => WHAT_test.new());
|
| isa_ok $o.a.WHAT, WHAT_ref, '.WHAT on attributes';
|
| isa_ok $o.b.WHAT, WHAT_test, '.WHAT on attributes of same type as class';
|
| my $r = WHAT_test.new();
|
| lives_ok {$r.b = $r}, 'type check on recursive data structure';
|
| isa_ok $r.b.WHAT, WHAT_test, '.WHAT on recursive data structure';
|
|
|
| }
|
|
|
| {
|
| class ClosureWithself {
|
| has $.cl = { self.foo }
|
| method foo { 42 }
|
| }
|
| is ClosureWithself.new.cl().(), 42, 'use of self in closure on RHS of attr init works';
|
| }
|
|
|
|
|
| # Tests for clone.
|
| {
|
| class CloneTest { has $.x is rw; has $.y is rw; }
|
| my $a = CloneTest.new(x => 1, y => 2);
|
| my $b = $a.clone();
|
| is $b.x, 1, 'attribute cloned';
|
| is $b.y, 2, 'attribute cloned';
|
| $b.x = 3;
|
| is $b.x, 3, 'changed attribute on clone...';
|
| is $a.x, 1, '...and original not affected';
|
| my $c = $a.clone(x => 42);
|
| is $c.x, 42, 'clone with parameters...';
|
| is $a.x, 1, '...leaves original intact...';
|
| is $c.y, 2, '...and copies what we did not change.';
|
| }
|
|
|
| # tests for *-1 indexing on classes, RT #61766
|
| {
|
| class ArrayAttribTest {
|
| has @.a is rw;
|
| method init {
|
| @.a = <a b c>;
|
| }
|
| method m0 { @.a[0] };
|
| method m1 { @.a[*-2] };
|
| method m2 { @.a[*-1] };
|
| }
|
| my $o = ArrayAttribTest.new;
|
| $o.init;
|
| is $o.m0, 'a', '@.a[0] works';
|
| is $o.m1, 'b', '@.a[*-2] works';
|
| is $o.m2, 'c', '@.a[*-1] works';
|
| }
|
|
|
| {
|
| class AttribWriteTest {
|
| has @.a;
|
| has %.h;
|
| method set_array1 {
|
| @.a = <c b a>;
|
| }
|
| method set_array2 {
|
| @!a = <c b a>;
|
| }
|
| method set_hash1 {
|
| %.h = (a => 1, b => 2);
|
| }
|
| method set_hash2 {
|
| %!h = (a => 1, b => 2);
|
| }
|
| }
|
|
|
| my $x = AttribWriteTest.new;
|
| # see Larry's reply to
|
| # http://groups.google.com/group/perl.perl6.language/browse_thread/thread/2bc6dfd8492b87a4/9189d19e30198ebe?pli=1
|
| # on why these should fail.
|
| dies_ok { $x.set_array1 }, 'can not assign to @.array attribute';
|
| dies_ok { $x.set_hash1 }, 'can not assign to %.hash attribute';
|
| lives_ok { $x.set_array2 }, 'can assign to @!array attribute';
|
| lives_ok { $x.set_hash2 }, 'can assign to %!hash attribute';
|
| }
|
|
|
| # test that whitespaces after 'has (' are allowed.
|
| # This used to be a Rakudo bug (RT #61914)
|
| {
|
| class AttribWsTest {
|
| has ( $.this,
|
| $.that,
|
| );
|
| }
|
| my AttribWsTest $o .= new( this => 3, that => 4);
|
| is $o.this, 3, 'could use whitespace after "has ("';
|
| is $o.that, 4, '.. and a newline within the has() declarator';
|
| }
|
|
|
| # test typed attributes and === (was Rakudo RT#62902).
|
| {
|
| class TA1 { }
|
| class TA2 {
|
| has TA1 $!a;
|
| method foo { $!a === TA1 }
|
| }
|
| ok(TA2.new.foo, '=== works on typed attribute initialized with proto-object');
|
| }
|
|
|
| # used to be pugs regression
|
| {
|
| class C_Test { has $.a; }
|
| sub f() { C_Test.new(:a(123)) }
|
| sub g() { my C_Test $x .= new(:a(123)); $x }
|
|
|
| is(C_Test.new(:a(123)).a, 123, 'C_Test.new().a worked');
|
|
|
| my $o = f();
|
| is($o.a, 123, 'my $o = f(); $o.a worked');
|
|
|
| is((try { f().a }), 123, 'f().a worked (so the pugsbug is fixed (part 1))');
|
|
|
| is((try { g().a }), 123, 'g().a worked (so the pugsbug is fixed (part 2))');
|
| }
|
|
|
| # was also a pugs regression:
|
| # Modification of list attributes created with constructor fails
|
|
|
| {
|
| class D_Test {
|
| has @.test is rw;
|
| method get () { shift @.test }
|
| }
|
|
|
| my $test1 = D_Test.new();
|
| $test1.test = [1];
|
| is($test1.test, [1], "Initialized outside constructor");
|
| is($test1.get , 1 , "Get appears to have worked");
|
| is($test1.test, [], "Get Worked!");
|
|
|
| my $test2 = D_Test.new( :test([1]) );
|
| is($test2.test, [1], "Initialized inside constructor");
|
| is($test2.get , 1 , "Get appears to have worked");
|
| is($test2.test, [], "Get Worked!");
|
| }
|
|
|
| # test typed attributes
|
| # TODO: same checks on private attributes
|
| {
|
| class TypedAttrib {
|
| has Int @.a is rw;
|
| has Int %.h is rw;
|
| has Int @!pa;
|
| has Int %!ph;
|
| method pac { @!pa.elems };
|
| method phc { %!ph.elems };
|
| }
|
| my $o = try { TypedAttrib.new };
|
| ok $o.defined, 'created object with typed attributes';
|
| is $o.a.elems, 0, 'typed public array attribute is empty';
|
| is $o.h.elems, 0, 'typed public hash attribute is empty';
|
| is $o.pac, 0, 'typed private array attribute is empty';
|
| is $o.phc, 0, 'typed private hash attribute is empty';
|
|
|
| ok $o.a.of === Int, 'array attribute is typed';
|
| lives_ok { $o.a = (2, 3) }, 'Can assign to typed drw-array-attrib';
|
| lives_ok { $o.a[2] = 4 }, 'Can insert into typed rw-array-attrib';
|
| lives_ok { $o.a.push: 5 }, 'Can push onto typed rw-array-attrib';
|
| is $o.a.join('|'), '2|3|4|5',
|
| '... all of the above actually worked (not only lived)';
|
|
|
| dies_ok { $o.a = <foo bar> }, 'type enforced on array attrib (assignment)';
|
| dies_ok { $o.a[2] = $*IN }, 'type enforced on array attrib (item assignment)';
|
| dies_ok { $o.a.push: [2, 3]}, 'type enforced on array attrib (push)';
|
| dies_ok { $o.a[42]<foo> = 3}, 'no autovivification (typed array)';
|
|
|
| #?rakudo todo 'over-eager auto-vivification bugs'
|
| is $o.a.join('|'), '2|3|4|5',
|
| '... all of the above actually did nothing (not just died)';
|
|
|
| ok $o.h.of === Int, 'hash attribute is typed';
|
| lives_ok {$o.h = { a => 1, b => 2 } }, 'assign to typed hash attrib';
|
| lives_ok {$o.h<c> = 3}, 'insertion into typed hash attrib';
|
| lives_ok {$o.h.push: (d => 4) }, 'pushing onto typed hash attrib';
|
| is_deeply $o.h<a b c d>, (1, 2, 3, 4), '... all of them worked';
|
|
|
| dies_ok {$o.h = { :a<b> } }, 'Type enforced (hash, assignment)';
|
| dies_ok {$o.h<a> = 'b' }, 'Type enforced (hash, insertion)';
|
| dies_ok {$o.h.push: (g => 'f') }, 'Type enforced (hash, push)';
|
| dies_ok {$o.h<blubb><bla> = 3 }, 'No autovivification (typed hash)';
|
| is_deeply $o.h<a b c d>, (1, 2, 3, 4), 'hash still unchanged';
|
| }
|
|
|
| # attribute initialization based upon other attributes
|
| {
|
| class AttrInitTest {
|
| has $.a = 1;
|
| has $.b = 2;
|
| has $.c = $.a + $.b;
|
| }
|
| is AttrInitTest.new.c, 3, 'Can initialize one attribute based on another (1)';
|
| is AttrInitTest.new(a => 2).c, 4, 'Can initialize one attribute based on another (2)';
|
| is AttrInitTest.new(c => 9).c, 9, 'Can initialize one attribute based on another (3)';
|
| }
|
|
|
| # attributes with & sigil
|
| {
|
| class CodeAttr1 { has &!m = sub { "ok" }; method f { &!m() } }
|
| is CodeAttr1.new.f, "ok", '&!m = sub { ... } works and an be called';
|
|
|
| class CodeAttr2 { has &.a = { "woot" }; method foo { &!a() } }
|
| is CodeAttr2.new.foo, "woot", '&.a = { ... } works and also declares &!a';
|
| is CodeAttr2.new.a().(), "woot", '&.a has accessor returning closure';
|
|
|
| class CodeAttr3 { has &!m = method { "OH HAI" }; method f { self.&!m() } }
|
| is CodeAttr3.new.f, 'OH HAI', '&!m = method { ... } and self.&!m() work';
|
| }
|
|
|
| {
|
| # from t/oo/class_inclusion_with_inherited_class.t
|
| # used to be a pugs regression
|
|
|
| role A {
|
| method t ( *@a ) {
|
| [+] @a;
|
| }
|
| }
|
|
|
| class B does A {}
|
|
|
| class C does A {
|
| has $.s is rw;
|
| has B $.b is rw;
|
| submethod BUILD {
|
| $.b = B.new;
|
| $.s = $.b.t(1, 2, 3);
|
| }
|
| }
|
|
|
| is C.new.s, 6, "Test class include another class which inherited from same role";
|
| }
|
|
|
| # RT #68370
|
| {
|
| class RT68370 {
|
| has $!a;
|
| method rt68370 { $!a = 68370 }
|
| }
|
|
|
| dies_ok { RT68370.rt68370() },
|
| 'dies: trying to modify instance attribute when invocant is type object';
|
| }
|
|
|
| # Binding an attribute (was RT #64850)
|
| #?rakudo skip 'null pmc access on binding an attribute'
|
| {
|
| class RT64850 {
|
| has $.x;
|
| method foo { $!x := 42 }
|
| }
|
| my $a = RT64850.new;
|
| $a.foo;
|
| is $a.x, 42, 'binding to an attribute works';
|
| }
|
|
|
| #?rakudo skip 'RT 73368'
|
| {
|
| class InitializationThunk {
|
| has $.foo = my $x = 5;
|
| method bar { $x };
|
| }
|
|
|
| is InitializationThunk.new.bar, 5, 'a lexical is not tied to a thunk';
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
Attributes are stored in an opaque datatype, not in a hash. Not even the class has to care how they're stored, since they're declared much like ordinary variables. Instead of my, use has:
class Dog is Mammal {
has $.name = "fido";
has $.tail is rw;
has @.legs;
has $!brain;
...
}
Public attributes have a secondary sigil of "dot", indicating the automatic generation of an accessor method of the same name. Private attributes use an exclamation to indicate that no public accessor is generated.
From t/spec/S12-attributes/instance.t lines 15–31 (no results): (skip)
| # L<S12/Attributes/the automatic generation of an accessor method of the same name>
|
|
|
| class Foo1 { has $.bar; };
|
|
|
| {
|
| my $foo = Foo1.new();
|
| ok($foo ~~ Foo1, '... our Foo1 instance was created');
|
| my $val;
|
| #?pugs 2 todo 'feature'
|
| lives_ok {
|
| $val = $foo.can("bar")
|
| }, '.. checking autogenerated accessor existence';
|
| ok($val, '... $foo.can("bar") should have returned true');
|
| ok($foo.bar().notdef, '.. autogenerated accessor works');
|
| ok($foo.bar.notdef, '.. autogenerated accessor works w/out parens');
|
| }
|
|
|
Highlighted:
small|full
From t/spec/S12-attributes/instance.t lines 67–87 (no results): (skip)
| # L<S12/Attributes/Private attributes use an exclamation to indicate that no public accessor is>
|
|
|
|
|
| {
|
| class Foo4 { has $!bar; };
|
| my $foo = Foo4.new();
|
| ok($foo ~~ Foo4, '... our Foo4 instance was created');
|
| #?pugs eval 'todo'
|
| ok(!$foo.can("bar"), '.. checking autogenerated accessor existence', );
|
| }
|
|
|
|
|
| {
|
| class Foo4a { has $!bar = "baz"; };
|
| my $foo = Foo4a.new();
|
| ok($foo ~~ Foo4a, '... our Foo4a instance was created');
|
| #?pugs eval 'todo'
|
| ok(!$foo.can("bar"), '.. checking autogenerated accessor existence');
|
| }
|
|
|
|
|
Highlighted:
small|full
has $!brain;
The "true name" of the private variable always has the exclamation, but much like with our variables, you may declare a lexically scoped alias to the private variable by saying:
has $brain; # also declares $!brain;
As with the ! declaration, no accessor is generated.
And any later references to the private variable within the same block may either use or omit the exclamation, as you wish to emphasize or ignore the privacy of the variable. Outside the block, you must use the ! form. If you declare with the ! form, you must use that form consistently everywhere. If you declare with the . form, you also get the private ! form as a non-virtual name for the actual storage location, and you may use either ! or . form anywhere within the class, even if the class is reopened. Outside the class you must use the public . form, or rely on a method call (which can be a private method call, but only for trusted classes).
For public attributes, some traits are copied to the accessor method. The rw trait causes the generated accessor to be declared rw, making it an lvalue method. The default is a read-only accessor.
From t/spec/S12-attributes/instance.t lines 44–66 (no results): (skip)
| # L<S12/Attributes/making it an lvalue method>
|
|
|
|
|
| #?pugs todo 'instance attributes'
|
| {
|
| class Foo3 { has $.bar is rw; };
|
| my $foo = Foo3.new();
|
| ok($foo ~~ Foo3, '... our Foo3 instance was created');
|
| my $val;
|
| lives_ok {
|
| $val = $foo.can("bar");
|
| }, '.. checking autogenerated accessor existence';
|
| ok $val, '... $foo.can("bar") should have returned true';
|
| ok($foo.bar().notdef, '.. autogenerated accessor works');
|
| lives_ok {
|
| $foo.bar = "baz";
|
| }, '.. autogenerated mutator as lvalue works';
|
| is($foo.bar, "baz", '.. autogenerated mutator as lvalue set the value correctly');
|
| #?rakudo 2 todo 'oo'
|
| lives_ok { $foo.bar("baz2"); }, '.. autogenerated mutator works as method';
|
| is $foo.bar, "baz2", '.. autogenerated mutator as method set the value correctly';
|
| }
|
|
|
Highlighted:
small|full
If you declare the class as rw, then all the class's attributes default to rw, much like a C struct.
From t/spec/S12-class/rw.t lines 7–32 (no results): (skip)
| # L<S12/Attributes/If you declare the class as>
|
|
|
| class Foo {
|
| has $.readonly_attr;
|
| }
|
|
|
| {
|
| my Foo $foo .= new;
|
| #?pugs todo 'bug'
|
| dies_ok { $foo.readonly_attr++ }, "basic sanity";
|
| }
|
|
|
|
|
| class Bar is rw {
|
| has $.readwrite_attr;
|
| }
|
|
|
| {
|
| my Bar $bar .= new;
|
| lives_ok { $bar.readwrite_attr++ },
|
| "'is rw' on the class declaration applies to all attributes (1)";
|
| is $bar.readwrite_attr, 1,
|
| "'is rw' on the class declaration applies to all attributes (2)";
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
You may write your own accessors to override any or all of the autogenerated ones.
The attribute variables may be used within instance methods to refer directly to the attribute values. Outside the instance methods, the only access to attributes is through the accessors since an object has to be specified. The dot form of attribute variables may be used in derived classes because the dot form always implies a virtual accessor call. Every dot declaration also declares a corresponding private exclamation storage location, and the exclamation form may be used only in the actual class, not in derived classes. Reference to the internal storage location via $!foo should generally be restricted to submethods. Ordinary methods should stick to the $.foo form.
In fact, within submethods, use of the $.foo form on attributes that are available as $!foo (that is, that are declared directly by this class) is illegal and produces a dire compile-time warning (which may be suppressed). Within a submethod the $.foo form may only be used on attributes from parent classes, because only the parent classes' part of the object is guaranteed to be in a consistent state (because BUILDALL call's the parent classes' BUILD routines first). If you attempt to get around this by declaring BUILD as a method rather than a submethod, that will also be flagged as a dire (but suppressible) compile-time warning. (It is possible to define an inheritable BUILD routine if you have access to all the metadata for the current class, but it's not easy, and it certainly doesn't happen by accident just because you change submethod to method.)
Because $.foo, @.foo, %.foo, &.foo are just shorthands of self.foo with different contexts, the class does not need to declare any of those as an attribute -- a method foo declaration can work just as well.
As with the normal method call forms, only dotless parentheses may contain arguments. If you use the .() form it will perform an extra level of indirection after the method call:
self.foo(1,2,3); # a regular method call
self.foo.(1,2,3); # self.foo().(1,2,3), call .() on closure returned by .foo
$.foo(1,2,3); # calls self.foo under $ context
$.foo.(1,2,3); # $.foo().(1,2,3), call .() on closure returned by .foo
&.foo(1,2,3); # calls self.foo under & context
&.foo.(1,2,3); # &.foo().(1,2,3), call .() on closure returned by .foo
Pseudo-assignment to an attribute declaration specifies the default value. The value on the right is treated as an implicit closure and evaluated at object build time, that is, when the object is being constructed, not when class is being composed. To refer to a value computed at compilation or composition time, you can either use a temporary or a temporal block of some sort:
From t/spec/S12-attributes/defaults.t lines 7–21 (no results): (skip)
| # L<S12/Attributes/The value on the right is evaluated at object build time>
|
|
|
| my $got_a_num; sub get_a_num { $got_a_num++; 42 }
|
| my $got_a_str; sub get_a_str { $got_a_str++; "Pugs" }
|
|
|
| my $got_a_code;
|
| my $was_in_closure;
|
| sub get_a_code {
|
| $got_a_code++;
|
| return {
|
| $was_in_closure++;
|
| 42;
|
| };
|
| }
|
|
|
Highlighted:
small|full
From t/spec/S12-attributes/instance.t lines 32–43 (no results): (skip)
| # L<S12/Attributes/Pseudo-assignment to an attribute declaration specifies the default>
|
|
|
| {
|
| class Foo2 { has $.bar = "baz"; };
|
| my $foo = Foo2.new();
|
| ok($foo ~~ Foo2, '... our Foo2 instance was created');
|
| ok($foo.can("bar"), '.. checking autogenerated accessor existence');
|
| is($foo.bar(), "baz", '.. autogenerated accessor works');
|
| is($foo.bar, "baz", '.. autogenerated accessor works w/out parens');
|
| dies_ok { $foo.bar = 'blubb' }, 'attributes are ro by default';
|
| }
|
|
|
Highlighted:
small|full
has $.r = rand; # each object gets different random value
constant $random = rand;
has $.r = $random; # every object gets same value
has $.r = BEGIN { rand };
has $.r = INIT { rand };
has $.r = ENTER { rand };
has $.r = FIRST { rand };
has $.r = constant $myrand = rand;
When it is called at BUILD time, the topic of the implicit closure will be the attribute being initialized, while "self" refers to the entire object being initialized. The closure will be called at the end of the BUILD only if the attribute is not otherwise initialized in either the signature or the body of the BUILD. The closure actually defines the body of an anonymous method, so self is available with whatever attributes are constructed by that point in time (including all parent attributes). The initializers are run in order of declaration within the class, so a given initializer may refer back to an attribute defined in a preceding has declaration.
From t/spec/S12-attributes/defaults.t lines 22–105 (no results): (skip)
| # L<S12/Attributes/the attribute being initialized>
|
|
|
| my $set_by_code_attr;
|
|
|
| class Foo {...}
|
| eval 'class Foo {
|
| has $.num = get_a_num();
|
| has $.str = { get_a_str() };
|
| has $.code = { get_a_code() };
|
|
|
| has $.set_by_code = {
|
| $set_by_code_attr := $_;
|
| 42;
|
| };
|
|
|
| has $.self_in_code = { self.echo };
|
|
|
| method echo { "echo" }
|
| }';
|
|
|
| {
|
| is $got_a_num, 1, "default should be called at compile-time";
|
| my Foo $foo .= new;
|
| is $got_a_num, 1, "default should be called only once, at compile-time (1)";
|
| is $foo.num, 42, "attribute default worked";
|
| is $got_a_num, 1, "default should be called only once, at compile-time (2)";
|
| }
|
|
|
| {
|
| $got_a_str = 0; # reset
|
|
|
| {
|
| my Foo $foo .= new;
|
| is $got_a_str, 1, "using a coderef as a default value delays execution";
|
| is try { $foo.str }, "Pugs", "attribute default worked";
|
| }
|
|
|
| {
|
| my Foo $foo .= new;
|
| is $got_a_str, 2, "using a coderef as a default value delays execution";
|
| is try { $foo.str }, "Pugs", "attribute default worked";
|
| }
|
| }
|
|
|
| {
|
| $got_a_code = 0; # reset
|
|
|
| {
|
| my Foo $foo .= new;
|
| is $got_a_code, 1, "using a coderef as a default value delays execution";
|
| is $was_in_closure, 0, "sub-coderef not yet executed";
|
| try { $foo.code };
|
| is $was_in_closure, 0, "sub-coderef still not executed";
|
| }
|
|
|
| {
|
| my Foo $foo .= new;
|
| is $got_a_code, 2, "using a coderef as a default value delays execution";
|
| is $was_in_closure, 0, "sub-coderef not yet executed";
|
| is try { $foo.code() }, 42, "sub-coderef execution works";
|
| is $was_in_closure, 1, "sub-coderef still not executed";
|
| }
|
| }
|
|
|
| {
|
| my Foo $foo .= new;
|
|
|
| is try { $foo.set_by_code }, 42, '$_ is the attribute being initialized (1)';
|
| is $set_by_code_attr, 42, '$_ is the attribute being initialized (2)';
|
|
|
| lives_ok { $set_by_code_attr++ },
|
| '$_ is the attribute being initialized (3)';
|
|
|
| is try { $foo.set_by_code }, 43, '$_ is the attribute being initialized (4)';
|
| is $set_by_code_attr, 43, '$_ is the attribute being initialized (5)';
|
| }
|
|
|
| {
|
| my Foo $foo .= new;
|
|
|
| is try { $foo.self_in_code }, "echo", "self is the object being initialized";
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
Class attributes are declared with either my or our. The only difference from ordinary my or our variables is that an accessor is generated according to the secondary sigil:
From t/spec/S12-attributes/recursive.t lines 31–45 (no results): (skip)
| #L<S12/Attributes/"Class attributes are declared">
|
| {
|
| class B {
|
| my B $.attr is rw;
|
| };
|
|
|
| my B $a;
|
| lives_ok {
|
| $a .= new();
|
| B.attr = $a;
|
| }, "Can instantiate class with recursively-typed class lexical";
|
| ok B.attr === $a, "Recursively-typed class lexical stores correctly";
|
|
|
| }
|
|
|
Highlighted:
small|full
From t/spec/S12-attributes/class.t lines 11–54 (no results): (skip)
| #L<S12/Attributes/"Class attributes are declared">
|
| #L<S12/Class methods/metaclass method always delegated>
|
|
|
| plan 28;
|
|
|
| class Foo {
|
| our $.bar = 23;
|
| our $.yada is rw = 13;
|
| }
|
|
|
| my $test = 0;
|
| ok ($test = Foo.bar), 'accessors for class attributes work';
|
| is $test, 23, 'class attributes really work';
|
|
|
| class Baz is Foo {};
|
|
|
| my $test2 = 0;
|
| lives_ok { $test2 = Baz.bar }, 'inherited class attribute accessors work';
|
| is $test2, 23, 'inherited class attributes really work';
|
|
|
| my $test3 = 0;
|
| lives_ok { Baz.yada = 42; $test3 = Baz.yada }, 'inherited rw class attribute accessors work';
|
| is $test3, 42, 'inherited rw class attributes really work';
|
|
|
| class Quux is Foo { has $.bar = 17; };
|
|
|
| my $test4 = 0;
|
| #?pugs 99 todo 'class attributes'
|
| lives_ok { $test4 = Quux.new() },
|
| 'Can instantiate with overridden instance method';
|
| is $test4.bar, 17, 'Instance call gets instance attribute, not class attribute';
|
| my $test5 = 0;
|
| dies_ok {$test5 = Quux.bar}, 'class attribute accessor hidden by accessor in subclass; we do not magically ignore it';
|
| #?rakudo 5 todo 'class attributes'
|
| is $test5, 23, 'class attribute really works, even when overridden';
|
| my $test6 = 0;
|
| lives_ok { $test6 = Quux.^bar}, 'class attribute accessible via ^name';
|
| is $test6, 23, 'class attribute via ^name really works';
|
| my $test7 = 0;
|
| #?pugs 2 todo 'feature'
|
| lives_ok {$test7 = $test4.^bar},
|
| 'class attribute accessible via ^name called on instance';
|
| is $test7, 23, 'class attribute via $obj.^name really works';
|
|
|
Highlighted:
small|full
our $.count; # generates a public read-only .count accessor
our %!cache is rw; # generates no public accessor
my $.count; # generates a public read-only .count accessor
my %!cache is rw; # generates no public accessor
From t/spec/S12-class/parent_attributes.t lines 17–36 (no results): (skip)
| # L<S12/Construction and Initialization/>
|
|
|
| class Foo {
|
| has $.x is rw;
|
| method boo { $.x }
|
| }
|
|
|
| class Bar is Foo {
|
| method set($v) { $.x = $v }
|
| }
|
|
|
| my Foo $u .= new(x => 5);
|
| is($u.boo, 5, 'set attribute');
|
|
|
| $u= Bar.new(Foo{ x => 12 });
|
| is($u.boo, 12, 'set parent attribute');
|
| $u.set(9);
|
| is($u.boo, 9, 'reset parent attribute');
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
From t/spec/S12-class/instantiate.t lines 7–38 (1 ā, 0 Ć): (skip)
| # L<S12/Construction and Initialization> |
| # Basic instantiation. |
ā | class Foo1 { }; |
| my $foo1 = Foo1.new(); |
| ok(defined($foo1), 'instantiated a class'); |
| |
| # Instantiation with initializing attributes. |
| class Foo2 { |
| has $!a; |
| has $!b; |
| method check { |
| $!a + $!b |
| } |
| } |
| my $foo2 = Foo2.new(:a(39), :b(3)); |
| is($foo2.check(), 42, 'initializing attributes in new'); |
| |
| # RT #62732 |
| { |
| eval 'NoSuchClass.new()'; |
| ok $! ~~ Exception, 'death to instantiating nonexistent class'; |
| ok "$!" ~~ / NoSuchClass /, |
| 'error for "NoSuchClass.new()" mentions NoSuchClass'; |
| |
| eval 'NoSuch::Subclass.new()'; |
| ok $! ~~ Exception, 'death to instantiating nonexistent::class'; |
| #?rakudo todo 'RT #62732' |
| ok "$!" ~~ / 'NoSuch::Subclass' /, |
| 'error for "NoSuch::Subclass.new()" mentions NoSuch::Subclass'; |
| } |
| |
| # vim: ft=perl6 |
Highlighted:
small|full
From t/spec/S12-construction/construction.t lines 7–79 (no results): (skip)
| # L<S12/"Construction and Initialization">
|
|
|
| class OwnConstr {
|
| has $.x = 13;
|
| my $in_own = 0;
|
| method own() {
|
| $in_own++;
|
| return self.bless(self.CREATE(), :x(42));
|
| }
|
| method in_own {
|
| $in_own;
|
| }
|
| }
|
| ok OwnConstr.new ~~ OwnConstr, "basic class instantiation";
|
| is OwnConstr.new.x, 13, "basic attribute access";
|
| # As usual, is instead of todo_is to suppress unexpected succeedings
|
| is OwnConstr.in_own, 0, "own constructor was not called";
|
|
|
| ok OwnConstr.own ~~ OwnConstr, "own construction instantiated its class";
|
| is OwnConstr.own.x, 42, "attribute was set from our constructor";
|
| #?rakudo todo 'unknown'
|
| is OwnConstr.in_own, 1, "own constructor was actually called";
|
|
|
|
|
| # L<"http://www.mail-archive.com/perl6-language@perl.org/msg20241.html">
|
| # provide constructor for single positional argument
|
|
|
| class Foo {
|
| has $.a;
|
|
|
| method new ($self: Str $string) {
|
| $self.bless(*, a => $string);
|
| }
|
| }
|
|
|
|
|
| ok Foo.new("a string") ~~ Foo, '... our Foo instance was created';
|
|
|
| #?pugs todo 'feature'
|
| is Foo.new("a string").a, 'a string', "our own 'new' was called";
|
|
|
|
|
| # Using ".=" to create an object
|
| {
|
| class Bar { has $.attr }
|
| my Bar $bar .= new(:attr(42));
|
| is $bar.attr, 42, "instantiating an object using .= worked (1)";
|
| }
|
| # Using ".=()" to create an object
|
| {
|
| class Fooz { has $.x }
|
| my Fooz $f .= new(:x(1));
|
| is $f.x, 1, "instantiating an object using .=() worked";
|
| }
|
|
|
| {
|
| class Baz { has @.x is rw }
|
| my Baz $foo .= new(:x(1,2,3));
|
| lives_ok -> { $foo.x[0] = 3 }, "Array initialized in auto-constructor is not unwritable...";
|
| is $foo.x[0], 3, "... and keeps its value properly."
|
| }
|
|
|
| # RT #64116
|
| {
|
| class RT64116 { has %.env is rw };
|
|
|
| my $a = RT64116.CREATE;
|
|
|
| lives_ok { $a.env = { foo => "bar" } }, 'assign to attr of .CREATEd class';
|
| is $a.env<foo>, 'bar', 'assignment works';
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
All classes inherit a default new constructor from Mu. It expects all arguments to be named parameters initializing attributes of the same name. You may write your own new to override the default, or write constructors with any other name you like. As in Perl 5, a constructor is any routine that calls bless. Unlike in Perl 5, you call it as a method on the class object (though any object may be used as a class object), passing the candidate as the first argument. To bless a hash as in Perl 5, say:
$object = $class.bless({k1 => $v1, k2 => $v2, ...});
However, the normal way to create a candidate to bless is by calling CREATE (which by default creates an opaque object):
$object = $class.bless($class.CREATE(), k1 => $v1, k2 => $v2, ...)
$object = $class.bless($class.CREATE(), :k1($v1), :k2($v2), ...) # same
Alternatively, you can pass Whatever and have bless call CREATE for you.
$object = $class.bless(*, k1 => $v1, k2 => $v2, ...)
In addition to the candidate positional argument, bless also allows one or more positional arguments representing autovivifying type objects. Such an object looks like a type name followed by a hash subscript (see "Autovivifying objects" below). These are used to initialize superclasses.
Other than the candidate object and any autovivifying type objects, all arguments to bless must be named arguments, not positional. Hence, the main purpose of custom constructors is to turn positional arguments into named arguments for bless. The bless method allows an object to be used for its class invocant. (Your constructor need not allow this). In any case, the object is not used as a prototype. Use .clone instead of .bless if that's what you mean.
Any named arguments to bless are automatically passed to the CREATE and BUILD routines. If you wish to pass special options to the CREATE routine (such as an alternate representation), call CREATE yourself and then pass the resulting candidate to .bless:
my $candidate = $class.CREATE(:repr<P6opaque>);
$object = $class.bless($candidate, :k1($v1), :k2($v2))
For the built-in default CREATE method, P6opaque is the default representation. Other possibilities are P6hash, P5hash, P5array, PyDict, Cstruct, etc.
The bless function automatically calls all appropriate BUILD routines by calling the BUILDALL routine for the current class, which initializes the object in least-derived to most-derived order. DESTROY and DESTROYALL work the same way, only in reverse.
From t/spec/S12-construction/destruction.t lines 7–46 (no results): (skip)
| # L<S12/"Construction and Initialization"/"DESTROY and DESTROYALL work the
|
| # same way, only in reverse">
|
|
|
| my $in_destructor = 0;
|
| my @destructor_order;
|
|
|
| class Foo
|
| {
|
| submethod DESTROY { $in_destructor++ }
|
| }
|
|
|
| class Parent
|
| {
|
| submethod DESTROY { push @destructor_order, 'Parent' }
|
| }
|
|
|
| class Child is Parent
|
| {
|
| submethod DESTROY { push @destructor_order, 'Child' }
|
| }
|
|
|
| my $foo = Foo.new();
|
| isa_ok($foo, Foo, 'basic instantiation of declared class' );
|
| ok( ! $in_destructor, 'destructor should not fire while object is active' );
|
|
|
| my $child = Child.new();
|
| undefine $child;
|
|
|
| # no guaranteed timely destruction, so replace $a and try to force some GC here
|
| for 1 .. 100
|
| {
|
| $foo = Foo.new();
|
| }
|
|
|
| ok( $in_destructor, '... only when object goes away everywhere' );
|
| is( +@destructor_order, 2, '... only as many as available DESTROY submethods' );
|
| is( @destructor_order[0], 'Child', 'Child DESTROY should fire first' );
|
| is( @destructor_order[1], 'Parent', '... then parent' );
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
The default BUILD and BUILDALL are inherited from Mu, so you need to write initialization routines only if you wish to modify the default behavior. The bless function automatically passes the appropriate argument list to the BUILD of its various parent classes. If the type of the parent class corresponds to one of the type objects passed to bless, that type object's argument list is used. Otherwise all the arguments to bless are passed to the parent class's BUILD. For the final BUILD of the current object, all the arguments to bless are passed to the BUILD, so it can deal with any type objects that need special handling. (It is allowed to pass type objects that don't correspond to any parent class.)
From t/spec/S12-construction/named-params-in-BUILD.t lines 6–23 (no results): (skip)
| # L<S12/Construction and Initialization/The default BUILD and BUILDALL>
|
|
|
| class Foo {
|
| has $.v;
|
| submethod BUILD (Str :$value) {
|
| $!v = $value;
|
| }
|
| }
|
|
|
| my $obj = Foo.new( value => 'bar' );
|
|
|
| is( $obj.v, 'bar',
|
| 'BUILD arg declared as named and invoked with literal pair should'
|
| ~ ' contain only the pair value' );
|
| isa_ok($obj.v, Str, 'same arg should be of declared type' );
|
| isa_ok($obj, Foo, 'The object was constructed of the right type');
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
From t/spec/S12-construction/BUILD.t lines 6–109 (no results): (skip)
| # L<S12/Construction and Initialization/The default BUILD and BUILDALL>
|
|
|
|
|
| class Parent {
|
| has Str $.gather is rw = '';
|
| has Int $.parent-counter is rw = 0;
|
| has Int $.child-counter is rw = 0;
|
| submethod BUILD (:$a) {
|
| $.parent-counter++;
|
| $.gather ~= "Parent(a): ($a)";
|
| }
|
| }
|
|
|
| class Child is Parent {
|
| submethod BUILD (:$a, :$b) {
|
| $.child-counter++;
|
| $.gather ~= " | Child(a, b): ($a, $b)";
|
| }
|
| }
|
|
|
| my $obj = Child.new(:b(5), :a(7));
|
|
|
| is $obj.parent-counter, 1, "Called Parent's BUILD method once";
|
| is $obj.child-counter, 1, "Called Child's BUILD method once";
|
| is $obj.gather, 'Parent(a): (7) | Child(a, b): (7, 5)',
|
| 'submethods were called in right order (Parent first)';
|
|
|
| # assigning to attributes during BUILD
|
| # multiple inheritance
|
| {
|
| class A_Parent1 {
|
| submethod BUILD() {
|
| $.reg('A_Parent1');
|
| }
|
| }
|
|
|
| class A_Parent2 {
|
| submethod BUILD() {
|
| $.reg('A_Parent2');
|
| }
|
| }
|
|
|
| class A_Child is A_Parent1 is A_Parent2 {
|
| submethod BUILD() {
|
| $.reg('A_Child');
|
| }
|
| }
|
|
|
| class A_GrandChild is A_Child {
|
| has $.initlist;
|
| method reg($x) { $!initlist ~= $x };
|
| submethod BUILD() {
|
| $.reg('A_GrandChild');
|
| }
|
| }
|
|
|
| my $x;
|
| lives_ok { $x = A_GrandChild.new() },
|
| "can call child's methods in parent's BUILD";
|
| ok ?($x.initlist eq 'A_Parent1A_Parent2A_ChildA_GrandChild'
|
| | 'A_Parent2A_Parent1A_ChildA_GrandChild'),
|
| 'initilized called in the right order (MI)';
|
| }
|
|
|
| # RT #63900
|
| {
|
| class RT63900_P {
|
| has %.counter is rw;
|
| submethod BUILD {
|
| %.counter{ 'BUILD' }++;
|
| }
|
| }
|
| class RT63900_C is RT63900_P {
|
| }
|
|
|
| my $c = RT63900_C.new();
|
| is $c.counter<BUILD>, 1, 'BUILD called once';
|
| }
|
|
|
| # RT #67888
|
| {
|
| my $counter = 0;
|
|
|
| class TestCompiler is Perl6::Compiler {
|
| submethod BUILD {
|
| $counter++;
|
| }
|
| }
|
|
|
| TestCompiler.new;
|
| #?rakudo todo 'RT #67888'
|
| is $counter, 1, "testing BUILD in compiler subclass";
|
| }
|
|
|
| {
|
| BEGIN { @*INC.push: 't/spec/packages' }
|
| use Test::Util;
|
| is_run
|
| 'class Foo { method BUILD() { ... } }',
|
| { out => '', err => /BUILD/ & /submethod/ },
|
| 'method BUILD produces a compile-time warning';
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
class Dog is Animal {...}
my $pet = Dog.new( :name<Fido>, Animal{ :blood<warm>, :legs(4) } );
Here we are using an autovivifying Animal type object to specify what the arguments to Animal's BUILD routine should look like. (It does not actually autovivify an Animal apart from the one being created.)
You can clone an object, changing some of the attributes:
From t/spec/S12-attributes/clone.t lines 7–54 (no results): (skip)
| # L<S12/Construction and Initialization/You can clone an object, changing some of the attributes:>
|
| class Foo {
|
| has $.attr;
|
| method set_attr ($attr) { $.attr = $attr; }
|
| method get_attr () { $.attr }
|
| }
|
|
|
| my $a = Foo.new(:attr(13));
|
| isa_ok($a, Foo);
|
| is($a.get_attr(), 13, '... got the right attr value');
|
|
|
| my $c = $a.clone();
|
| isa_ok($c, Foo);
|
| is($c.get_attr(), 13, '... cloned object retained attr value');
|
|
|
| my $val;
|
| lives_ok {
|
| $val = $c === $a;
|
| }, "... cloned object isn't identity equal to the original object";
|
| ok($val.defined && !$val, "... cloned object isn't identity equal to the original object");
|
|
|
| my $d;
|
| lives_ok {
|
| $d = $a.clone(attr => 42)
|
| }, '... cloning with supplying a new attribute value';
|
|
|
| my $val2;
|
| lives_ok {
|
| $val2 = $d.get_attr()
|
| }, '... getting attr from cloned value';
|
| is($val2, 42, '... cloned object has proper attr value');
|
|
|
| # Test to cover RT#62828, which exposed a bad interaction between while loops
|
| # and cloning.
|
| {
|
| class A {
|
| has $.b;
|
| };
|
| while shift [A.new( :b(0) )] -> $a {
|
| is($a.b, 0, 'sanity before clone');
|
| my $x = $a.clone( :b($a.b + 1) );
|
| is($a.b, 0, 'clone did not change value in original object');
|
| is($x.b, 1, 'however, in the clone it was changed');
|
| last;
|
| }
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
$newdog = $olddog.clone(:trick<RollOver>);
You can write your own BUILD submethod to control initialization. If you name an attribute as a parameter, that attribute is initialized directly, so
From t/spec/S12-attributes/instance.t lines 127–188 (no results): (skip)
| # L<S12/Construction and Initialization/If you name an attribute as a parameter, that attribute is initialized directly, so>
|
|
|
|
|
| {
|
| class Foo6 {
|
| has $.bar is rw;
|
| has $.baz is rw;
|
| has $!hidden;
|
|
|
| submethod BUILD($.bar, $.baz, $!hidden) {}
|
| method get_hidden() { $!hidden }
|
| }
|
|
|
| my $foo = Foo6.new(bar => 1, baz => 2, hidden => 3);
|
| ok($foo ~~ Foo6, '... our Foo6 instance was created');
|
|
|
| is($foo.bar, 1, "getting a public rw attribute (1)" );
|
| is($foo.baz, 2, "getting a public ro attribute (2)" );
|
| is($foo.get_hidden, 3, "getting a private ro attribute (3)" );
|
| }
|
|
|
| # check that doing something in submethod BUILD works
|
|
|
| {
|
| class Foo6a {
|
| has $.bar is rw;
|
| has $.baz is rw;
|
| has $!hidden;
|
|
|
| submethod BUILD ($!hidden, $.bar = 10, $.baz?) {
|
| $.baz = 5;
|
| }
|
| method get_hidden() { $!hidden }
|
| }
|
|
|
| my $foo = Foo6a.new(bar => 1, hidden => 3);
|
| ok($foo ~~ Foo6a, '... our Foo6a instance was created');
|
|
|
| is($foo.bar, 1, "getting a public rw attribute (1)" );
|
| is($foo.baz, 5, "getting a public rw attribute (2)" );
|
| is($foo.get_hidden, 3, "getting a private ro attribute (3)" );
|
| }
|
|
|
| # check that assignment in submethod BUILD works with a bare return, too
|
| {
|
| class Foo6b {
|
| has $.bar is rw;
|
| has $.baz is rw;
|
|
|
| submethod BUILD ($.bar = 10, $.baz?) {
|
| $!baz = 9;
|
| return;
|
| }
|
| }
|
|
|
| my $foo = Foo6b.new(bar => 7);
|
| ok($foo ~~ Foo6b, '... our Foo6b instance was created');
|
|
|
| is($foo.bar, 7, "getting a public rw attribute (1)" );
|
| is($foo.baz, 9, "getting a public rw attribute (2)" );
|
| }
|
|
|
Highlighted:
small|full
submethod BUILD ($!tail, $!legs) {}
is equivalent to
submethod BUILD ($tail is copy, $legs is copy) {
$!tail := $tail;
$!legs := $legs;
}
Whether you write your own BUILD or not, at the end of the BUILD, any default attribute values are implicitly copied into any attributes that haven't otherwise been initialized.
From t/spec/S03-operators/inplace.t lines 51–78 (no results): (skip)
| # L<S12/"Mutating methods">
|
| my @b = <z a b d e>;
|
| @b .= sort;
|
| is ~@b, "a b d e z", "inplace sort";
|
|
|
| #?rakudo skip "Doubtful Error: Cannot assign to readonly value"
|
| {
|
| $_ = -42;
|
| .=abs;
|
| is($_, 42, '.=foo form works on $_');
|
| }
|
|
|
| # RT #64268
|
| {
|
| my @a = 1,3,2;
|
| my @a_orig = @a;
|
|
|
| my @b = @a.sort: {1};
|
| is @b, @a_orig, 'worked: @a.sort: {1}';
|
|
|
| @a.=sort: {1};
|
| is @a, @a_orig, 'worked: @a.=sort: {1}';
|
|
|
| @a.=sort;
|
| is @a, [1,2,3], 'worked: @a.=sort';
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
You can call an in-place mutator method like this:
@array .= sort;
If there is a self:sort operator defined, that will be used. Otherwise one will be autogenerated from the ordinary sort operator, on the order of:
@array = @array.sort;
One handy place for an in-place mutator is to call a constructor on a variable of a known type:
my Dog $spot .= new(:tail<LONG>, :legs<SHORT>);
From t/spec/S12-introspection/walk.t lines 13–107 (no results): (skip)
| #L<S12/Calling sets of methods>
|
|
|
| class A {
|
| method m { 'A' }
|
| }
|
| class B {
|
| method m { 'B' }
|
| }
|
| class C is A is B {
|
| method m { 'C' }
|
| method n { 'OH NOES' }
|
| }
|
| class D is A {
|
| method m { 'D' }
|
| }
|
| class E is C is D {
|
| method m { 'E' }
|
| }
|
|
|
| sub cand_order(@cands, $instance) {
|
| my $result = '';
|
| for @cands -> $cand {
|
| $result ~= $cand($instance);
|
| }
|
| $result
|
| }
|
|
|
| # :canonical
|
| {
|
| my $x = E.new;
|
| my @cands = $x.WALK(:name<m>, :canonical);
|
| is cand_order(@cands, $x), 'ECDAB', ':canonical (explicit) works';
|
| @cands = $x.WALK(:name<m>);
|
| is cand_order(@cands, $x), 'ECDAB', ':canonical (as default) works';
|
| }
|
|
|
| # :super
|
| {
|
| my $x = E.new;
|
| my @cands = $x.WALK(:name<m>, :super);
|
| is cand_order(@cands, $x), 'CD', ':super works';
|
| }
|
|
|
| # :breadth
|
| {
|
| my $x = E.new;
|
| my @cands = $x.WALK(:name<m>, :breadth);
|
| is cand_order(@cands, $x), 'ECDAB', ':breadth works';
|
| }
|
|
|
| # :descendant
|
| {
|
| my $x = E.new;
|
| my @cands = $x.WALK(:name<m>, :descendant);
|
| is cand_order(@cands, $x), 'ABCDE', ':descendant works';
|
| }
|
|
|
| # :ascendant
|
| {
|
| my $x = E.new;
|
| my @cands = $x.WALK(:name<m>, :ascendant);
|
| is cand_order(@cands, $x), 'ECABD', ':ascendant works';
|
| }
|
|
|
| # :preorder
|
| {
|
| my $x = E.new;
|
| my @cands = $x.WALK(:name<m>, :preorder);
|
| is cand_order(@cands, $x), 'ECABD', ':preorder works';
|
| }
|
|
|
| # :omit
|
| {
|
| my $x = E.new;
|
| my @cands = $x.WALK(:name<m>, :omit({ .^can('n') }));
|
| is cand_order(@cands, $x), 'DAB', ':omit works';
|
| }
|
|
|
| # :include
|
| {
|
| my $x = E.new;
|
| my @cands = $x.WALK(:name<m>, :include(regex { <[CDE]> }));
|
| #?rakudo skip ':include fails'
|
| is cand_order(@cands, $x), 'ECD', ':include works';
|
| }
|
|
|
| # :include and :omit
|
| {
|
| my $x = E.new;
|
| my @cands = $x.WALK(:name<m>, :include(regex { <[CDE]> }), :omit({ .^can('n') }));
|
| #?rakudo skip ':include/:omit together fail'
|
| is cand_order(@cands, $x), 'D', ':include and :omit together work';
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
From t/spec/S12-methods/calling_sets.t lines 4–180 (no results): (skip)
| # L<S12/"Calling sets of methods">
|
|
|
| # Some basic tests with only single-dispatch in operation.
|
| class A {
|
| has $.cnt is rw;
|
| method foo { $.cnt += 4 }
|
| }
|
| class B is A {
|
| method foo { $.cnt += 2 }
|
| }
|
| class C is B {
|
| method foo { $.cnt += 1 }
|
| }
|
|
|
| {
|
| my $c = C.new();
|
|
|
| $c.cnt = 0;
|
| $c.?foo();
|
| is $c.cnt, 1, '.? calls first matching method';
|
|
|
| $c.cnt = 0;
|
| $c.*foo();
|
| is $c.cnt, 7, '.* calls up inheritance hierarchy';
|
|
|
| $c.cnt = 0;
|
| $c.+foo();
|
| is $c.cnt, 7, '.+ calls up inheritance hierarchy';
|
|
|
| ok !defined($c.?bar()), '.? on undefined method gives undefined';
|
|
|
| my $lived = 0;
|
| try { $c.+bar(); $lived = 1; }
|
| is $lived, 0, '.+ on undefined method is an error';
|
|
|
| is ($c.*bar()).elems, 0, '.* on undefined method gives empty list';
|
|
|
| my $foo = "foo";
|
|
|
| $c.cnt = 0;
|
| $c.?"$foo"();
|
| is $c.cnt, 1, '.? with dynamic method name';
|
|
|
| $c.cnt = 0;
|
| $c.*"$foo"();
|
| is $c.cnt, 7, '.* with dynamic method name';
|
|
|
| $c.cnt = 0;
|
| $c.+"$foo"();
|
| is $c.cnt, 7, '.+ with dynamic method name';
|
|
|
| $lived = 0;
|
| try { $c."?foo"(); $lived = 1; }
|
| is $lived, 0, '? at start of dynamic name does not imply .?';
|
|
|
| $lived = 0;
|
| try { $c."+foo"(); $lived = 1; }
|
| is $lived, 0, '+ at start of dynamic name does not imply .+';
|
|
|
| $lived = 0;
|
| try { $c."*foo"(); $lived = 1; }
|
| is $lived, 0, '* at start of dynamic name does not imply .*';
|
| }
|
|
|
|
|
| # Some tests involiving .?, .+ and .* with multi-methods.
|
| class D {
|
| has $.cnt is rw;
|
| multi method foo() { $.cnt++ }
|
| multi method foo(Int $x) { $.cnt++ }
|
| multi method foo($x) { $.cnt++ }
|
| }
|
| class E is D {
|
| multi method foo() { $.cnt++ }
|
| multi method foo($x) { $.cnt++ }
|
| }
|
|
|
| {
|
| my $e = E.new();
|
|
|
| $e.cnt = 0;
|
| $e.foo();
|
| is $e.cnt, 1, 'dispatch to one sanity test';
|
|
|
| $e.cnt = 0;
|
| $e.?foo();
|
| is $e.cnt, 1, '.? calls first matching multi method';
|
|
|
| $e.cnt = 0;
|
| $e.*foo();
|
| is $e.cnt, 2, '.* calls up inheritance hierarchy and all possible multis';
|
|
|
| $e.cnt = 0;
|
| $e.*foo(2.5);
|
| is $e.cnt, 2, '.* calls up inheritance hierarchy and all possible multis';
|
|
|
| $e.cnt = 0;
|
| $e.*foo(2);
|
| is $e.cnt, 3, '.* calls up inheritance hierarchy and all possible multis';
|
|
|
| $e.cnt = 0;
|
| $e.+foo();
|
| is $e.cnt, 2, '.+ calls up inheritance hierarchy and all possible multis';
|
|
|
| $e.cnt = 0;
|
| $e.+foo(2.5);
|
| is $e.cnt, 2, '.+ calls up inheritance hierarchy and all possible multis';
|
|
|
| $e.cnt = 0;
|
| $e.+foo(2);
|
| is $e.cnt, 3, '.+ calls up inheritance hierarchy and all possible multis';
|
|
|
| ok !defined($e.?foo("lol", "no", "match")), '.? when no possible multis gives undefined';
|
|
|
| my $lived = 0;
|
| try { $e.+foo("lol", "no", "match"); $lived = 1; }
|
| is $lived, 0, '.+ with no matching multis is an error';
|
|
|
| is ($e.*foo("lol", "no", "match")).elems, 0, '.* when no possible multis gives empty list';
|
| }
|
|
|
| # Some tests to make sure we walk methods from roles too.
|
| role R1 {
|
| multi method mm { $.cnt += 1 }
|
| multi method sm { $.cnt += 2 }
|
| }
|
| role R2 {
|
| multi method mm { $.cnt += 3 }
|
| }
|
| class F does R1 {
|
| has $.cnt is rw;
|
| }
|
| class G is F does R2 {
|
| }
|
|
|
| {
|
| my $g = G.new();
|
|
|
| $g.cnt = 0;
|
| $g.?sm();
|
| is $g.cnt, 2, 'single dispatch method from role found with .?';
|
|
|
| $g.cnt = 0;
|
| $g.+sm();
|
| is $g.cnt, 2, 'single dispatch method from role found with .+';
|
|
|
| $g.cnt = 0;
|
| $g.*sm();
|
| is $g.cnt, 2, 'single dispatch method from role found with .*';
|
|
|
| $g.cnt = 0;
|
| $g.?mm();
|
| is $g.cnt, 3, 'multi dispatch method from role found with .?';
|
|
|
| $g.cnt = 0;
|
| $g.+mm();
|
| is $g.cnt, 4, 'multi dispatch method from role found with .+';
|
|
|
| $g.cnt = 0;
|
| $g.*mm();
|
| is $g.cnt, 4, 'multi dispatch method from role found with .*';
|
| }
|
|
|
| class MMT1 {
|
| multi method foo($x) { 42 }
|
| }
|
| class MMT2 is MMT1 {
|
| multi method foo(Int $x) { "oh noes" }
|
| }
|
| is MMT2.new.?foo("lol"), 42, '.? when initial multi does not match will find next one up';
|
|
|
| {
|
| my @list = MMT1.new.?nonexistent();
|
| is +@list, 0, '.?nonexisent() returns Nil';
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
For any method name, there may be some number of candidate methods that could handle the request: typically, inherited methods or multi variants. The ordinary "dot" operator dispatches to a method in the standard fashion. There are also "dot" variants that call some number of methods with the same name:
$object.meth(@args) # calls one method or dies
$object.?meth(@args) # calls method if there is one, otherwise Nil
$object.*meth(@args) # calls all methods (0 or more)
$object.+meth(@args) # calls all methods (1 or more)
The method name may be quoted when disambiguation is needed:
$object."+meth"(@args)
$object.'VAR'(@args)
As with ordinary calls, the identifier supplying the literal method name may be replaced with an interpolated quote to specify the method name indirectly. It may also be replaced with an array to specify the exact list of candidates to be considered:
my @candidates := $object.WALK(:name<foo>, :breadth, :omit($?CLASS));
$object.*@candidates(@args);
The WALK method takes these arguments:
:canonical # canonical dispatch order
:ascendant # most-derived first, like destruction order
:descendant # least-derived first, like construction order
:preorder # like Perl 5 dispatch
:breadth # like multi dispatch
:super # only immediate parent classes
:name<name> # only classes containing named method declaration
:omit(Selector) # only classes that don't match selector
:include(Selector) # only classes that match selector
Any method can defer to the next candidate method in the list by the special functions callsame, callwith, nextsame, and nextwith. The "same" variants reuse the original argument list passed to the current method, whereas the "with" variants allow a new argument list to be substituted for the rest of the candidates. The "call" variants dispatch to the rest of the candidates and return their values to the current method for subsequent processing, whereas while the "next" variants don't return, but merely defer to the rest of the candidate list:
From t/spec/S12-methods/defer-next.t lines 7–84 (no results): (skip)
| # L<S12/"Calling sets of methods"/"Any method can defer to the next candidate method in the list">
|
|
|
| # Simple test, making sure nextwith passes on parameters properly.
|
| class A {
|
| method a(*@A) {
|
| (self.perl, @A)
|
| }
|
| }
|
| class B is A {
|
| method a() {
|
| nextwith("FIRST ARG", "SECOND ARG")
|
| }
|
| }
|
| {
|
| my $instance = B.new;
|
| my @result = $instance.a();
|
| is @result.elems, 3, 'nextwith passed on right number of parameters';
|
| is @result[0], $instance.perl, 'invocant passed on correctly';
|
| is @result[1], "FIRST ARG", 'first argument correct';
|
| is @result[2], "SECOND ARG", 'second argument correct';
|
| }
|
|
|
| class Foo {
|
| # $.tracker is used to determine the order of calls.
|
| has $.tracker is rw;
|
| multi method doit() {$.tracker ~= 'foo,'}
|
| multi method doit(Int $num) {$.tracker ~= 'fooint,'}
|
| method show {$.tracker}
|
| method clear {$.tracker = ''}
|
| }
|
|
|
| class BarNextSame is Foo {
|
| multi method doit() {$.tracker ~= 'bar,'; nextsame; $.tracker ~= 'ret1,'}
|
| multi method doit(Int $num) {$.tracker ~= 'barint,'; nextsame; $.tracker ~= 'ret2,'}
|
| }
|
|
|
| {
|
| my $o = BarNextSame.new;
|
| $o.clear;
|
| $o.doit;
|
| is($o.show, 'bar,foo,', 'nextsame inheritance test');
|
| $o.clear;
|
| is($o.show, '', 'sanity test for clearing');
|
| $o.doit(5);
|
| is($o.show, 'barint,fooint,', 'nextsame multimethod/inheritance test');
|
| }
|
|
|
| class BarNextWithEmpty is Foo {
|
| multi method doit() {$.tracker ~= 'bar,'; nextwith(); $.tracker ~= 'ret1,'}
|
| multi method doit(Int $num) {$.tracker ~= 'barint,'; nextwith(); $.tracker ~= 'ret2,'}
|
| }
|
| {
|
| my $o = BarNextWithEmpty.new;
|
| $o.clear;
|
| $o.doit;
|
| is($o.show, 'bar,foo,', 'nextwith() inheritance test');
|
| $o.clear;
|
| is($o.show, '', 'sanity test for clearing');
|
| $o.doit(5);
|
| is($o.show, 'barint,foo,', 'nextwith() multimethod/inheritance test');
|
| }
|
|
|
| class BarNextWithInt is Foo {
|
| multi method doit() {$.tracker ~= 'bar,'; nextwith(42); $.tracker ~= 'ret1,'}
|
| multi method doit(Int $num) {$.tracker ~= 'barint,'; nextwith(42); $.tracker ~= 'ret2,'}
|
| }
|
| {
|
| my $o = BarNextWithInt.new;
|
| $o.clear;
|
| $o.doit;
|
| is($o.show, 'bar,fooint,', 'nextwith(42) inheritance test');
|
| $o.clear;
|
| is($o.show, '', 'sanity test for clearing');
|
| $o.doit(5);
|
| is($o.show, 'barint,fooint,', 'nextwith(42) multimethod/inheritance test');
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
From t/spec/S12-methods/defer-call.t lines 7–87 (no results): (skip)
| # L<S12/"Calling sets of methods"/"Any method can defer to the next candidate method in the list">
|
|
|
| # Simple test, making sure callwith passes on parameters properly.
|
| class A {
|
| method a(*@A) {
|
| (self.perl, @A)
|
| }
|
| }
|
| class B is A {
|
| method a() {
|
| callwith("FIRST ARG", "SECOND ARG")
|
| }
|
| }
|
| {
|
| my $instance = B.new;
|
| my @result = $instance.a();
|
| is @result.elems, 3, 'nextwith passed on right number of parameters';
|
| is @result[0], $instance.perl, 'invocant passed on correctly';
|
| is @result[1], "FIRST ARG", 'first argument correct';
|
| is @result[2], "SECOND ARG", 'second argument correct';
|
| }
|
|
|
| class Foo {
|
| # $.tracker is used to determine the order of calls.
|
| has $.tracker is rw;
|
| multi method doit() {$.tracker ~= 'foo,'}
|
| multi method doit(Int $num) {$.tracker ~= 'fooint,'}
|
| method show {$.tracker}
|
| method clear {$.tracker = ''}
|
| }
|
|
|
| class BarCallSame is Foo {
|
| multi method doit() {$.tracker ~= 'bar,'; callsame; $.tracker ~= 'ret1,'}
|
| multi method doit(Int $num) {$.tracker ~= 'barint,'; callsame; $.tracker ~= 'ret2,'}
|
| }
|
|
|
| {
|
| my $o = BarCallSame.new;
|
| $o.clear;
|
| $o.doit;
|
| is($o.show, 'bar,foo,ret1,', 'callsame inheritance test');
|
| $o.clear;
|
| is($o.show, '', 'sanity test for clearing');
|
| $o.doit(5);
|
| is($o.show, 'barint,fooint,ret2,', 'callsame multimethod/inheritance test');
|
| }
|
|
|
|
|
| class BarCallWithEmpty is Foo {
|
| multi method doit() {$.tracker ~= 'bar,'; callwith(); $.tracker ~= 'ret1,'}
|
| multi method doit(Int $num) {$.tracker ~= 'barint,'; callwith(); $.tracker ~= 'ret2,'}
|
| }
|
| {
|
| my $o = BarCallWithEmpty.new;
|
| $o.clear;
|
| $o.doit;
|
| is($o.show, 'bar,foo,ret1,', 'callwith() inheritance test');
|
| $o.clear;
|
| is($o.show, '', 'sanity test for clearing');
|
| {
|
| $o.doit(5);
|
| is($o.show, 'barint,foo,ret2,', 'callwith() multimethod/inheritance test');
|
| }
|
| }
|
|
|
| class BarCallWithInt is Foo {
|
| multi method doit() {$.tracker ~= 'bar,'; callwith(42); $.tracker ~= 'ret1,'}
|
| multi method doit(Int $num) {$.tracker ~= 'barint,'; callwith(42); $.tracker ~= 'ret2,'}
|
| }
|
| {
|
| my $o = BarCallWithInt.new;
|
| $o.clear;
|
| $o.doit;
|
| is($o.show, 'bar,fooint,ret1,', 'callwith(42) inheritance test');
|
| $o.clear;
|
| is($o.show, '', 'sanity test for clearing');
|
| $o.doit(5);
|
| is($o.show, 'barint,fooint,ret2,', 'callwith(42) multimethod/inheritance test');
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
callsame; # call with the original arguments (return here)
callwith(); # call with no arguments (return here)
callwith(1,2,3); # call with a new set of arguments (return here)
nextsame; # redispatch with the original arguments (no return)
nextwith(); # redispatch with no arguments (no return)
nextwith(1,2,3); # redispatch with a new set of arguments (no return)
For dispatches using . and .?, the return value is the Capture returned by the first method completed without deferring. (Such a return value may in fact be failure, but it still counts as a successful call from the standpoint of the dispatcher.) Likewise the return value of .* and .+ is a list of Captures returned by those methods that ran to completion without deferring to next method.
It is also possible to trim the candidate list so that the current call is considered the final candidate. (This is implicitly the case already for the dispatch variants that want a single successful call.) For the multiple call variants, lastcall will cause the dispatcher to throw away the rest of the candidate list, and the subsequent return from the current method will produce the final Capture in the returned list. (If you were already on the last call of the candidate list, no candidates are thrown away, only the list. So you can't accidentally throw away the wrong list by running off the end, since the candidate list is ordinarily not thrown away by the dispatcher until after the last call.)
From t/spec/S12-methods/lastcall.t lines 7–54 (no results): (skip)
| # L<S12/"Calling sets of methods"/"It is also possible to trim the candidate list so that the current call is considered the final candidate.">
|
|
|
| class Foo {
|
| # $.tracker is used to determine the order of calls.
|
| has $.tracker is rw;
|
| method doit($foo) { $.tracker ~= 'foo,' }
|
| method show {$.tracker}
|
| method clear {$.tracker = ''}
|
| }
|
|
|
| class BazLastCallNext is Foo {
|
| multi method doit($foo) { $.tracker ~= 'baz,'; nextsame; }
|
| multi method doit(Int $foo) {
|
| $.tracker ~= 'bazint,';
|
| if 1 { lastcall }
|
| nextsame;
|
| $.tracker ~= 'ret3,';
|
| }
|
| }
|
|
|
| {
|
| my $o = BazLastCallNext.new;
|
| $o.clear;
|
| $o.doit("");
|
| is($o.show, 'baz,foo,', 'no lastcall, so we defer up the inheritance tree');
|
| $o.clear;
|
| is($o.show, '', 'sanity test for clearing');
|
| $o.doit(5);
|
| is($o.show, 'bazint,ret3,', 'lastcall meant nextsame failed, no deferal happened');
|
| }
|
|
|
| class BarLastCallSame is Foo {
|
| multi method doit($foo) {$.tracker ~= 'bar,'; lastcall; callsame; $.tracker ~= 'ret1,'}
|
| multi method doit(Int $num) {$.tracker ~= 'barint,'; callsame; $.tracker ~= 'ret2,'}
|
| }
|
|
|
| {
|
| my $o = BarLastCallSame.new;
|
| $o.clear;
|
| $o.doit("");
|
| is($o.show, 'bar,ret1,', 'lastcall trims candidate list, so no call up inheritance tree');
|
| $o.clear;
|
| is($o.show, '', 'sanity test for clearing');
|
| $o.doit(5);
|
| is($o.show, 'barint,bar,ret1,ret2,', 'lastcall trimming does not affect stuff earlier in chain');
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
Since it's possible to be dispatching within more than one candidate list at a time, these control flow calls are defined to apply only to the dynamically innermost dispatcher. If, for instance, you have a single dispatch that then calls into a multiple dispatch on the multi methods within a class, nextsame would go to the next best multi method within the class, not the next method candidate in the original single dispatch. This is not a bad limitation, since dispatch loops are dynamically scoped; to get to the outermost lists you can "pop" unwanted candidate lists using lastcall:
lastcall; nextsame; # call next in grandparent dispatcher loop
[Conjecture: if necessary, lastcall could have an argument or invocant to specify which kind of a dispatch loop we think we're throwing away, in case we're not sure about our context. This confusion could arise since we use nextsame semantics at least three different ways: single dispatch, multiple dispatch, and routine wrapper dispatch.]
Any of the method call forms may be turned into a hyperoperator by treating the method call as a postfix:
From t/spec/S12-methods/parallel-dispatch.t lines 7–113 (no results): (skip)
| # L<S12/"Parallel dispatch"/"Any of the method call forms may be turned into a hyperoperator">
|
| # syn r14547
|
|
|
| class Foo {
|
| has $.count is rw;
|
| method doit {$.count++}
|
| method !priv {$.count++}
|
| }
|
|
|
| class Bar is Foo {
|
| method doit {$.count++;}
|
| }
|
|
|
| {
|
| my @o = (5..10).map({Foo.new(count => $_)});
|
| is(@o.map({.count}), (5..10), 'object sanity test');
|
| @oĀ».doit;
|
| is(@o.map({.count}), (6..11), 'parallel dispatch using Ā» works');
|
| @o>>.doit;
|
| is(@o.map({.count}), (7..12), 'parallel dispatch using >> works');
|
| @oĀ»!priv;
|
| is(@o.map({.count}), (8..13), 'parallel dispatch to a private using Ā»! works');
|
| @o>>!priv;
|
| is(@o.map({.count}), (9..14), 'parallel dispatch to a private using >>! works');
|
| }
|
|
|
| {
|
| my @o = (5..10).map({Foo.new(count => $_)});
|
| is(@o.map({.count}), (5..10), 'object sanity test');
|
| lives_ok({@oĀ».?not_here}, 'parallel dispatch using @oĀ».?not_here lives');
|
| lives_ok({@o>>.?not_here}, 'parallel dispatch using @o>>.?not_here lives');
|
| dies_ok({@oĀ».not_here}, 'parallel dispatch using @oĀ».not_here dies');
|
| dies_ok({@o>>.not_here}, 'parallel dispatch using @o>>.not_here dies');
|
|
|
| @oĀ».?doit;
|
| is(@o.map({.count}), (6..11), 'parallel dispatch using @oĀ».?doit works');
|
| @o>>.?doit;
|
| is(@o.map({.count}), (7..12), 'parallel dispatch using @o>>.?doit works');
|
| #?rakudo 2 todo 'is_deeply does not think map results are the same as list on LHS'
|
| is_deeply @oĀ».?not_here, @o.map({ Nil }),
|
| '$objĀ».?nonexistingmethod returns a list of Nil';
|
| is_deeply @oĀ».?count, @o.map({.count}),
|
| '$objĀ».?existingmethod returns a list of the return values';
|
| }
|
|
|
| {
|
| my @o = (5..10).map({Bar.new(count => $_)});
|
| is(@o.map({.count}), (5..10), 'object sanity test');
|
| lives_ok({@oĀ».*not_here}, 'parallel dispatch using @oĀ».*not_here lives');
|
| lives_ok({@o>>.*not_here}, 'parallel dispatch using @o>>.*not_here lives');
|
| dies_ok({@oĀ».+not_here}, 'parallel dispatch using @oĀ».+not_here dies');
|
| dies_ok({@o>>.+not_here}, 'parallel dispatch using @o>>.+not_here dies');
|
|
|
| @oĀ».*doit;
|
| is(@o.map({.count}), (7..12), 'parallel dispatch using @oĀ».*doit works');
|
| @oĀ».+doit;
|
| is(@o.map({.count}), (9..14), 'parallel dispatch using @oĀ».*doit works');
|
| }
|
|
|
| {
|
| is(<a bc def ghij klmno>Ā».chars, (1, 2, 3, 4, 5), '<list>Ā».method works');
|
| is(<a bc def ghij klmno>>>.chars, (1, 2, 3, 4, 5), '<list>>>.method works');
|
| }
|
|
|
| {
|
| my @a = -1, 2, -3;
|
| my @b = -1, 2, -3;
|
| @aĀ».=abs;
|
| is(@a, [1,2,3], '@listĀ».=method works');
|
| @b>>.=abs;
|
| is(@b, [1,2,3], '@list>>.=method works');
|
| }
|
|
|
| # more return value checking
|
| {
|
| class PDTest {
|
| has $.data;
|
| multi method mul(Int $a) {
|
| $.data * $a;
|
| }
|
| multi method mul(Num $a) {
|
| $.data * $a.Int * 2
|
| }
|
| }
|
|
|
| my @a = (1..3).map: { PDTest.new(data => $_ ) };
|
| my $method = 'mul';
|
|
|
| is_deeply @aĀ».mul(3), (3, 6, 9), 'return value of @aĀ».method(@args)';
|
| is_deeply @aĀ»."$method"(3), (3, 6, 9), '... indirect';
|
|
|
| is_deeply @aĀ».?mul(3), (3, 6, 9), 'return value of @aĀ».?method(@args)';
|
| is_deeply @aĀ».?"$method"(3), (3, 6, 9), '... indirect';
|
|
|
| #?rakudo 4 todo 'is_deeply does not think map results are the same as list on LHS'
|
| is_deeply @aĀ».+mul(2), ([2, 4], [4, 8], [6, 12]),
|
| 'return value of @aĀ».+method is a list of lists';
|
| is_deeply @aĀ».+"$method"(2), ([2, 4], [4, 8], [6, 12]),
|
| '... indirect';
|
|
|
| is_deeply @aĀ».*mul(2), ([2, 4], [4, 8], [6, 12]),
|
| 'return value of @aĀ».*method is a list of lists';
|
| is_deeply @aĀ».*"$method"(2), ([2, 4], [4, 8], [6, 12]),
|
| '... indirect';
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
@object».meth(@args) # calls one method on each
@object».?meth(@args) # calls method if there is one on each
@object».*meth(@args) # calls all methods (0 or more) on each
@object».+meth(@args) # calls all methods (1 or more) on each
@object».=meth(@args) # calls mutator method on each
@object»!meth(@args) # calls private method on each
The return value is a list with exactly the same number of elements as @object. Each such return value is a Parcel or List of Parcel as specified above for the non-hyper "dot" variants.
Hyperoperators treat a junction as a scalar value, so saying:
$junction».meth(@args);
is just like:
$junction.meth(@args);
As with other forms of method call, the "meth" above may be replaced with a quoted string or variable to do various forms of indirection.
Note that, as with any hyper operator, the methods may be evaluated in any order (although the method results are always returned in the same order as the list of invocants). Use an explicit loop if you want to do something with ordered side effects, such as I/O.
From t/spec/S06-multi/positional-vs-named.t lines 8–68 (no results): (skip)
| #L<S12/"Multisubs and Multimethods">
|
|
|
| # the single parameter cases named and positional below - part of RT 53814
|
|
|
| multi earth (:$me) {"me $me"};
|
| multi earth (:$him) {"him $him"};
|
| multi earth (:$me, :$him) {"me $me him $him"};
|
| multi earth (:$me, :$him, :$her) {"me $me him $him her $her"};
|
| multi earth ($me) {"pos $me"};
|
| multi earth ($me, :$you) {"pos $me you $you"};
|
| multi earth ($me, :$her) {"pos $me her $her"};
|
| multi earth ($me, $you) {"pos $me pos $you"};
|
| multi earth ($me, $you, :$her) {"pos $me pos $you her $her"};
|
|
|
| is( earth(me => 1), 'me 1', 'named me');
|
| is( earth(him => 2), 'him 2', 'named you');
|
| is( earth(me => 1, him => 2), 'me 1 him 2', 'named me, named him');
|
| is( earth(him => 2, me => 1), 'me 1 him 2', 'named him, named me');
|
| is( earth(me => 1, him => 2, her => 3), 'me 1 him 2 her 3', 'named me named him named her');
|
| is( earth(him => 2, me => 1, her => 3), 'me 1 him 2 her 3', 'named him named me named her');
|
| is( earth(her => 3, me => 1, him => 2), 'me 1 him 2 her 3', 'named her named me named him');
|
| is( earth(her => 3, him => 2, me => 1), 'me 1 him 2 her 3', 'named her named him named me');
|
|
|
| is( earth('a'), 'pos a', 'pos');
|
| is( earth('b', you => 4), 'pos b you 4', 'pos, named you');
|
| is( earth('c', her => 3), 'pos c her 3', 'pos, named her');
|
| is( earth('d', 'e'), 'pos d pos e', 'pos, pos');
|
| is( earth('f', 'g', her => 3), 'pos f pos g her 3', 'pos, pos, named');
|
|
|
|
|
| # ensure we get the same results when the subroutines are
|
| # defined in reverse order
|
| #
|
|
|
| multi wind ($me, $you, :$her) {"pos $me pos $you her $her"};
|
| multi wind ($me, $you) {"pos $me pos $you"};
|
| multi wind ($me, :$her) {"pos $me her $her"};
|
| multi wind ($me, :$you) {"pos $me you $you"};
|
| multi wind ($me) {"pos $me"};
|
| multi wind (:$me, :$him, :$her) {"me $me him $him her $her"};
|
| multi wind (:$me, :$him) {"me $me him $him"};
|
| multi wind (:$him) {"him $him"};
|
| multi wind (:$me) {"me $me"};
|
|
|
| is( wind(me => 1), 'me 1', 'named me');
|
| is( wind(him => 2), 'him 2', 'named you');
|
| is( wind(me => 1, him => 2), 'me 1 him 2', 'named me, named him');
|
| is( wind(him => 2, me => 1), 'me 1 him 2', 'named him, named me');
|
| is( wind(me => 1, him => 2, her => 3), 'me 1 him 2 her 3', 'named me named him named her');
|
| is( wind(him => 2, me => 1, her => 3), 'me 1 him 2 her 3', 'named him named me named her');
|
| is( wind(her => 3, me => 1, him => 2), 'me 1 him 2 her 3', 'named her named me named him');
|
| is( wind(her => 3, him => 2, me => 1), 'me 1 him 2 her 3', 'named her named him named me');
|
|
|
| is( wind('a'), 'pos a', 'pos');
|
| is( wind('b', you => 4), 'pos b you 4', 'pos, named you');
|
| is( wind('c', her => 3), 'pos c her 3', 'pos, named her');
|
| is( wind('d', 'e'), 'pos d pos e', 'pos, pos');
|
| is( wind('f', 'g', her => 3), 'pos f pos g her 3', 'pos, pos, named');
|
|
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
From t/spec/S06-multi/unpackability.t lines 5–29 (no results): (skip)
| # L<S12/"Multisubs and Multimethods">
|
| # L<S06/Unpacking array parameters>
|
|
|
| multi sub foo ([$a]) { return "one" }
|
| multi sub foo ([$a,$b]) { return "two" }
|
| multi sub foo ([$a,$b,$c]) { return "three" }
|
| multi sub foo (*[$a,$b,$c,$d]) { return "four" }
|
|
|
| my @a = (1);
|
| my @b = (1,2);
|
| my @c = (1,2,3);
|
| my @d = (1,2,3,4);
|
|
|
| is foo(@a), "one", "multi dispatch on array packed with one element";
|
| is foo(@b), "two", "multi dispatch on array packed with two elements";
|
| is foo(@c), "three", "multi dispatch on array packed with three elements";
|
| is foo(@d), "four", "multi dispatch on array packed with four elements";
|
| is foo(1,2,3,4), "four", "multi dispatch on slurpy packed with four elements";
|
|
|
| multi sub bar ([$a,$b?]) { return "$a|$b" }
|
| multi sub bar (*[$a,$b,$c?]) { return "$a+$b+$c" }
|
|
|
| is bar(@a), "1|Any()", "multi dispatch on array packed with one required element + no optional";
|
| is bar(@b), "1|2", "multi dispatch on array packed with one required element + one optional";
|
| is bar(1,2,3), "1+2+3", "multi dispatch on slurpy packed with two required element + one optional";
|
Highlighted:
small|full
From t/spec/S06-multi/type-based.t lines 8–37 (no results): (skip)
| #L<S12/"Multisubs and Multimethods">
|
|
|
| multi foo (Int $bar) { "Int " ~ $bar }
|
| multi foo (Str $bar) { "Str " ~ $bar }
|
| multi foo (Rat $bar) { "Rat " ~ $bar }
|
| multi foo (Bool $bar) { "Bool " ~ $bar }
|
| multi foo (Regex $bar) { "Regex " ~ WHAT( $bar ) } # since Rule's don't stringify
|
| multi foo (Sub $bar) { "Sub " ~ $bar() }
|
| multi foo (@bar) { "Positional " ~ join(', ', @bar) }
|
| multi foo (%bar) { "Associative " ~ join(', ', %bar.keys.sort) }
|
| multi foo (IO $fh) { "IO" }
|
|
|
| is(foo('test'), 'Str test', 'dispatched to the Str sub');
|
| is(foo(2), 'Int 2', 'dispatched to the Int sub');
|
|
|
| my $num = '4';
|
| is(foo(1.4), 'Rat 1.4', 'dispatched to the Num sub');
|
| is(foo(1 == 1), 'Bool 1', 'dispatched to the Bool sub');
|
| is(foo(/a/),'Regex Regex()','dispatched to the Rule sub');
|
| is(foo(sub { 'baz' }), 'Sub baz', 'dispatched to the Sub sub');
|
|
|
| my @array = ('foo', 'bar', 'baz');
|
| is(foo(@array), 'Positional foo, bar, baz', 'dispatched to the Positional sub');
|
|
|
| my %hash = ('foo' => 1, 'bar' => 2, 'baz' => 3);
|
| is(foo(%hash), 'Associative bar, baz, foo', 'dispatched to the Associative sub');
|
|
|
| is(foo($*ERR), 'IO', 'dispatched to the IO sub');
|
|
|
| # You're allowed to omit the "sub" when declaring a multi sub.
|
Highlighted:
small|full
From t/spec/S12-methods/multi.t lines 7–200 (no results): (skip)
| # L<S12/"Multisubs and Multimethods">
|
| # L<S12/"Multi dispatch">
|
|
|
| class Foo {
|
| multi method bar() {
|
| return "Foo.bar() called with no args";
|
| }
|
|
|
| multi method bar(Str $str) {
|
| return "Foo.bar() called with Str : $str";
|
| }
|
|
|
| multi method bar(Int $int) {
|
| return "Foo.bar() called with Int : $int";
|
| }
|
|
|
| multi method bar(Num $num) {
|
| return "Foo.bar() called with Num : $num";
|
| }
|
|
|
| multi method baz($f) {
|
| return "Foo.baz() called with parm : $f";
|
| }
|
| }
|
|
|
|
|
| my $foo = Foo.new();
|
| is($foo.bar(), 'Foo.bar() called with no args', '... multi-method dispatched on no args');
|
|
|
| is($foo.bar("Hello"), 'Foo.bar() called with Str : Hello', '... multi-method dispatched on Str');
|
|
|
| is($foo.bar(5), 'Foo.bar() called with Int : 5', '... multi-method dispatched on Int');
|
| my $num = '4';
|
| is($foo.bar(+$num), 'Foo.bar() called with Num : 4', '... multi-method dispatched on Num');
|
|
|
| #?rakudo todo 'RT #66006'
|
| eval '$foo.baz()';
|
| ok ~$! ~~ /:i argument[s?]/, 'Call with wrong number of args should complain about args';
|
|
|
| role R1 {
|
| method foo($x) { 1 }
|
| }
|
| role R2 {
|
| method foo($x, $y) { 2 }
|
| }
|
| eval_dies_ok 'class X does R1 does R2 { }', 'sanity: get composition conflict error';
|
| class C does R1 does R2 {
|
| proto method foo() { "proto" }
|
| }
|
| my $obj = C.new;
|
| is($obj.foo(), 'proto', 'proto caused methods from roles to be composed without conflict');
|
| is($obj.foo('a'), 1, 'method composed into multi from role called');
|
| is($obj.foo('a','b'), 2, 'method composed into multi from role called');
|
|
|
|
|
| class Foo2 {
|
| multi method a($d) {
|
| "Any-method in Foo";
|
| }
|
| }
|
| class Bar is Foo2 {
|
| multi method a(Int $d) {
|
| "Int-method in Bar";
|
| }
|
| }
|
|
|
| is Bar.new.a("not an Int"), 'Any-method in Foo';
|
|
|
| # RT #67024
|
| #?rakudo todo 'redefintion of non-multi method (RT #67024)'
|
| {
|
| eval 'class A { method a(){0}; method a($x){1} }';
|
| ok $! ~~ Exception, 'redefintion of non-multi method (RT 67024)';
|
| ok "$!" ~~ /multi/, 'error message mentions multi-ness';
|
| }
|
|
|
| {
|
| role R3 {
|
| has @.order;
|
| multi method b() { @.order.push( 'role' ) }
|
| }
|
| class C3 does R3 {
|
| multi method b() { @.order.push( 'class' ); nextsame }
|
| }
|
|
|
| my $c = C3.new;
|
| lives_ok { $c.b }, 'can call multi-method from class with role';
|
|
|
| is $c.order, <class role>, 'call order is correct for class and role'
|
| }
|
|
|
| {
|
| role R4 {
|
| has @.order;
|
| multi method b() { @.order.push( 'role' ); nextsame }
|
| }
|
| class P4 {
|
| method b() { @.order.push( 'parent' ) }
|
| }
|
| class C4 is P4 does R4 {
|
| multi method b() { @.order.push( 'class' ); nextsame }
|
| }
|
| my $c = C4.new;
|
| lives_ok { $c.b }, 'call multi-method from class with parent and role';
|
|
|
| is $c.order, <class role parent>,
|
| 'call order is correct for class, role, parent'
|
| }
|
|
|
| # RT 69192
|
| {
|
| role R5 {
|
| multi method rt69192() { push @.order, 'empty' }
|
| multi method rt69192(Str $a) { push @.order, 'Str' }
|
| }
|
| role R6 {
|
| multi method rt69192(Num $a) { push @.order, 'Num' }
|
| }
|
| class RT69192 { has @.order }
|
|
|
| {
|
| my RT69192 $bot .= new();
|
| ($bot does R5) does R6;
|
| $bot.*rt69192;
|
| is $bot.order, <empty>, 'multi method called once on empty signature';
|
| }
|
|
|
| {
|
| my RT69192 $bot .= new();
|
| ($bot does R5) does R6;
|
| $bot.*rt69192('RT #69192');
|
| is $bot.order, <Str>, 'multi method called once on Str signature';
|
| }
|
|
|
| {
|
| my RT69192 $bot .= new();
|
| ($bot does R5) does R6;
|
| $bot.*rt69192( 69192 );
|
| is $bot.order, <Num>, 'multi method called once on Num signature';
|
| }
|
| }
|
|
|
| {
|
| role RoleS {
|
| multi method d( Str $x ) { 'string' }
|
| }
|
| role RoleI {
|
| multi method d( Int $x ) { 'integer' }
|
| }
|
| class M does RoleS does RoleI {
|
| multi method d( Any $x ) { 'any' }
|
| }
|
|
|
| my M $m .= new;
|
|
|
| is $m.d( 876 ), 'integer', 'dispatch to one role';
|
| is $m.d( '7' ), 'string', 'dispatch to other role';
|
| is $m.d( 1.2 ), 'any', 'dispatch to the class with the roles';
|
|
|
| my @multi_method = $m.^methods.grep({ ~$_ eq 'd' });
|
| is @multi_method.elems, 1, '.^methods returns one element for a multi';
|
|
|
| my $routine = @multi_method[0];
|
| #?rakudo todo 'multi method appears as Routine per r27045'
|
| ok $routine ~~ Routine, 'multi method from ^methods is a Routine';
|
| my @candies = $routine.candidates;
|
| is @candies.elems, 3, 'got three candidates for multi method';
|
|
|
| ok @candies[0] ~~ Method, 'candidate 0 is a method';
|
| ok @candies[1] ~~ Method, 'candidate 1 is a method';
|
| ok @candies[2] ~~ Method, 'candidate 2 is a method';
|
| }
|
|
|
| {
|
| class BrokenTie {
|
| multi method has_tie(Int $x) { 'tie1' };
|
| multi method has_tie(Int $y) { 'tie2' };
|
| }
|
|
|
| #?rakudo todo 'ambiguous dispatch should die'
|
| dies_ok { BrokenTie.has_tie( 42 ) }, 'call to tied method dies';
|
|
|
| class WorkingTie is BrokenTie {
|
| multi method has_tie(Int $z) { 'tie3' };
|
| multi method has_tie(Str $s) { 'tie4' };
|
| }
|
|
|
| is WorkingTie.has_tie( 42 ), 'tie3', 'broken class fixed by subclass (1)';
|
| is WorkingTie.has_tie( 'x' ), 'tie4', 'broken class fixed by subclass (2)';
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
The "long name" of a subroutine or method includes the type signature of its invocant arguments. The "short name" doesn't. If you put multi in front of any sub (or method) declaration, it allows multiple long names to share a short name, provided all of them are declared multi. (Putting proto on the first such declaration has the same effect, but usually you want to put the multi explicitly anyway for documentation.) If a sub (or method) is not marked with multi and it is not within the package or lexical scope of a proto of the same short name, it is considered unique, an only sub. You may mark a sub explicitly as only if you're worried it might be within the scope of a proto, and you want to suppress any other declarations within this scope. An only sub (or method) doesn't share with anything outside of it or declared prior to it. Only one such sub (or method) can inhabit a given namespace, and it hides any outer subs (or less-derived methods) of the same short name.
The default proto declarations provided by Perl from the global scope are not automatically propagated to the user's scope unless explicitly imported, so a sub declaration there that happens to be the same as a global multi is considered only unless explicitly marked multi. In the absence of such an explicit sub declaration, however, the global proto is used by the compiler in the analysis of any calls to that short name. (Since only list operators may be post-declared, as soon as the compiler sees a non-listop operator it is free to apply the global proto since any user-defined only version of it must of necessity be declared earlier in the user's lexical scope or not at all.)
A proto may share dispatch with multis declared after it in the same scope, but in that case it functions only as the final tie-breaker if the inner multis can't decide among themselves what to do. (It may then, of course, decide to redispatch outside of the current scope.)
Within its scope, the signature of a proto also nails down the presumed order and naming of positional parameters, so that any multi call with named arguments in that scope can presume to rearrange those arguments into positional parameters based on that information. (Unrecognized names remain named arguments.) Any other type information or traits attached to the proto are also shared by the routines within its scope, so a proto definition can be used to factor out common traits. This is particularly useful for establishing grammatical categories in a grammar by declaring a proto token or proto rule. (Perl 6's grammar does this, for instance.)
You can have multiple multi variables of the same name in the same scope, and they all share the same storage location and type. Usually these are declared by one proto declaration at the top, and leaving the multi implicit on the rest of the declarations. You might do this when you suspect you'll have multiple declarations of the same variable name (such code might be produced by a macro or by a code generator, for instance) and you wish to suppress any possible warnings about redefinition.
In contrast, multi routines can have only one instance of the long name in any namespace, and that instance hides any outer (or less-derived) routines with the same long name. It does not hide any routines with the same short name but a different long name. In other words, multis with the same short name can come from several different namespaces provided their long names differ and their short names aren't hidden by an only declaration in some intermediate scope.
When you call a routine with a particular short name, if there are multiple visible long names, they are all considered candidates. They are sorted into an order according to how close the run-time types of the arguments match up with the declared types of the parameters of each candidate. The best candidate is called, unless there's a tie, in which case the tied candidates are redispatched using any additional tiebreaker strategies (see below). For the purpose of this nominal typing, no constrained type is considered to be a type name; instead the constrained type is unwound into its base type plus constraint. Only the base type upon which the constrained type is based is considered for the nominal type match (along with the fact that it is constrained). That is, if you have a parameter:
subset Odd of Int where { $_ % 2 }
multi foo (Odd $i) {...}
it is treated as if you'd instead said:
multi foo (Int $i where { $_ % 2 }) {...}
Any constrained type is considered to have a base type that is "epsilon" narrower than the corresponding unconstrained type. The compile-time topological sort takes into account the presence of at least one constraint, but nothing about the number or nature of any additional constraints. If we think of Int' as any constrained version of Int, then Int' is always tighter nominally than Int. (Int' is a meta-notation, not Perl 6 syntax.)
The order in which candidates are considered is defined by a topological sort based on the "type narrowness" of each candidate's long name, where that in turn depends on the narrowness of each parameter that is participating. Identical types are considered tied. Parameters whose types are not comparable are also considered tied. A candidate is considered narrower than another candidate if at least one of its parameters is narrower and all the rest of its parameters are either narrower or tied. Also, if the signature has any additional required parameters not participating in the long name, the signature as a whole is considered epsilon tighter than any signature without extra parameters. In essence, the remaining arguments are added to the longname as if the user had declared a capture parameter to bind the rest of the arguments, and that capture parameter has a constraint that it must bind successfully to the additional required parameters. All such signatures within a given rank are considered equivalent, and subject to tiebreaker A below.
This defines the partial ordering of all the candidates. If the topological sort detects a circularity in the partial ordering, all candidates in the circle are considered tied. A warning will be issued at CHECK time if this is detected and there is no suitable tiebreaker that could break the tie.
There are three tiebreaking modes, in increasing order of desperation:
A) run-time constraint processing
B) use of a candidate marked with "is default"
C) use of a candidate marked as "proto"
In the absence of any constraints, ties in the nominal typing immediately failover to tiebreaker B or C; if not resolved by B or C, they warn at compile time about an ambiguous dispatch.
If there are any tied candidates with constraints, it follows from our definitions above that all of them are considered to be constrained. In the presence of longname parameters with constraints, or the implied constraint of extra required arguments, tiebreaker A is applied. Candidates which are tied nominally but have constraints are considered to be a completely different situation, insofar as it is assumed the user knows exactly why each candidate has the extra constraints it has. Thus, constrained signatures are considered to be much more like a switch defined by the user. So for tiebreaker A the candidates are simply called in the order they were declared, and the first one that successfully binds (and completes without calling nextsame or nextwith) is considered the winner, and all the other tied candidates are ignored. If all the constrained candidates fail, we throw out the rank of constrained variants and proceed to the next tighter rank, which may consist of the unconstrained variants without extra arguments.
For ranks that are not decided by constraint (tiebreaker A), tiebreaker B is used: only candidates marked with the default trait are considered, and the best matching default routine is used. If there are no default routines, or if the available defaults are also tied, tiebreaker C is used: a final tie-breaking proto sub is called, if there is one (see above). Otherwise the dispatch fails.
From t/spec/S12-methods/default-trait.t lines 6–30 (no results): (skip)
| # L<S12/Multisubs and Multimethods/"only candidates marked with the default
|
| # trait">
|
|
|
| class Something {
|
| multi method doit(Int $x) { 2 * $x };
|
| multi method doit(Int $x) is default { 3 * $x };
|
| }
|
|
|
| my $obj = Something.new();
|
| lives_ok { $obj.doit(3) }, "'is default' trait makes otherwise ambiguous method dispatch live";
|
| is $obj.doit(3), 9, "'is default' trait tie-breaks on method dispatch";
|
|
|
| multi sub doit_sub(Int $x) { 2 * $x };
|
| multi sub doit_sub(Int $x) is default { 3 * $x };
|
|
|
| lives_ok { doit_sub(3) }, "'is default' trait makes otherwise ambiguous method dispatch live";
|
| is doit_sub(3), 9, "'is default' trait on subs";
|
|
|
| multi sub slurpy() is default { return 'a' };
|
| multi sub slurpy(*@args) { return 'b' };
|
|
|
| is slurpy(2), 'b', 'basic sanity with arity based dispatch and slurpies';
|
| is slurpy(), 'a', '"is default" trait wins against empty slurpy param';
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
Ordinarily all the parameters of a multi sub are considered for dispatch. Here's a declaration for an integer range operator with two parameters in its long name:
multi sub infix:<..>(Int $min, Int $max) {...}
Sometimes you want to have parameters that aren't counted as part of the long name. For instance, if you want to allow an optional "step" parameter to your range operator, but not consider it for multi dispatch, then put a double semicolon instead of a comma before it:
multi sub infix:<..>(Int $min, Int $max;; Int $by = 1) {...}
The double semicolon, if any, determines the complete long name of a multi. (In the absence of that, a double semicolon is assumed after the last declared argument, but before any return signature.) Note that a call to the routine must still be compatible with subsequent arguments.
Note that the $by is not a required parameter, so doesn't impose the kind of constraint that allows tiebreaker A. If the default were omitted, it would be a required parameter, and subject to tiebreaker A. Likewise an ordinary named parameter does not participate as a tiebreaker, but you can mark named parameters as required to effectively make a switch based on named binding:
multi foo (Int $a;; :$x!) {...} # constrained
multi foo (Int $a;; :$y!) {...} # constrained
multi foo (Int $a;; :$z!) {...} # constrained
multi foo (Int $a;; *%_) {...} # unconstrained
The first three are dispatched under tiebreaker A as a constrained rank. If none of them can match, the final one is dispatched as an unconstrained rank, since *%_ is not considered a required parameter.
Likewise, constrained types sort before unconstrained:
multi bar (Even $a) {...} # constrained
multi bar (Odd $a) {...} # constrained
multi bar (Int $a) {...} # unconstrained
And values used as subset types also sort first, and are dispatched on a first-to-match basis:
multi baz (0) {...} # constrained
multi baz (1) {...} # constrained
multi baz (Int $x) {...} # unconstrained
If some of the constrained candidates come by import from other modules, they are all considered to be declared at the point of of importation for purposes of tiebreaking; subsequent tiebreaking is provided by the original order in the used module.
[Conjecture: However, a given multi may advertise multiple long names, some of which are shorter than the complete long name. This is done by putting a semicolon after each advertised long name (replacing the comma, if present). A semicolon has the effect of inserting two candidates into the list. One of them is inserted with exactly the same types, as if the semicolon were a comma. The other is inserted as if all the types after the semicolon were of type Any, which puts it later in the list than the narrower actual candidate. This merely determines its sort order; the candidate uses its real type signature if the dispatcher gets to it after rejecting all earlier entries on the candidate list. If that set of delayed candidates also contains ties, then additional semicolons have the same effect within that sublist of ties. Note, however, that semicolon is a no-op if the types after it are all Any. (As a limiting case, putting a semicolon after every parameter produces dispatch semantics much like Common Lisp. And putting a semicolon after only the first argument is much like ordinary single-dispatch methods.) Note: This single-semicolon syntax is merely to be considered reserved until we understand the semantics of it, and more importantly, the pragamatics of it (that is, whether it has any valid use case). Until then only the double-semicolon form will be implemented in the standard language.]
Within a class, multi submethod is visible to both method-dispatch and subroutine-dispatch. A multi method never participates in the subroutine-dispatch process. It is dispatched just like a normal method, then the tie-breaking rules of the previous paragraph are applied. That is, the shortest long name of a multi method includes only the single invocant, and any additional semicolons may only indicate long names to be used as tiebreakers.
The multi-method tiebreaking happens only within a given class; all parent classes' multis appear to the outside world to be only methods (and indeed, a foreign object may have no clue how to advertise multiple methods anyway). In other words, longnames from different classes don't intermix as do the longnames in ordinary multi-sub dispatch. So multi methods work only within a class; outside the class, single-dispatch semantics are enforced to preserve encapsulation. To put it another way, multi methods are only for convenience of implementation within a given class; and specifically to make it easier to compose roles with similar but not identical methods into a single class.
Conjecture: In order to specify dispatch that includes the return type context, it is necessary to place the return type before the double semicolon:
multi infix:<..>(Int $min, Int $max --> Iterator;; Int $by = 1) {...}
multi infix:<..>(Int $min, Int $max --> Selector;; Int $by = 1) {...}
Note that such a declaration might have to delay dispatch until the actual desired type is known! (Generally, you might just consider returning a flexible Range object instead of an anonymous partial dispatch that may or may not be resolved at compile time via type inferencing. Therefore return-type tiebreaking need not be supported in 6.0.0 unless some enterprising soul decides to make it work.)
From t/spec/S12-methods/method-vs-sub.t lines 5–32 (no results): (skip)
| #L<S12/Method call vs. Subroutine call>
|
|
|
| class test {
|
| method foo($a:) { 'method' }
|
| };
|
| sub foo($a) { 'sub' };
|
| my $obj = test.new;
|
|
|
| #?rakudo skip 'confused near "($obj:), "'
|
| is foo($obj:), 'method', 'method with colon notation';
|
| is $obj.foo, 'method', 'method with dot notation';
|
| is foo($obj), 'sub', 'adding trailing comma should call the "sub"';
|
|
|
| # RT #69610
|
| {
|
| class RT69610 {
|
| method rt69610() {
|
| return self;
|
| }
|
| }
|
|
|
| ok( { "foo" => &RT69610::rt69610 }.<foo>( RT69610.new ) ~~ RT69610,
|
| "Can return from method called from a hash lookup (RT 69610)" );
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
The caller indicates whether to make a method call or subroutine call by the call syntax. The "dot" form and the indirect object form default to method calls. All other prefix calls default to subroutine calls. This applies to prefix unary operators as well:
!$obj; # same as $obj.prefix:<!>
A method call considers only methods (including multi-methods and submethods) from the class hierarchy of its invocant, and fails if none is found. The object in question is in charge of interpreting the meaning of the method name, so if the object is a foreign object, the name will be interpreted by that foreign runtime.
A subroutine call considers only visible subroutines (including submethods) of that name. The object itself has no say in the dispatch; the subroutine dispatcher considers only the types the arguments involved, along with the name. Hence foreign objects passed to subroutines are forced to follow Perl semantics (to the extent foreign types can be coerced into Perl types, otherwise they fail).
There is no fail-over either from subroutine to method dispatch or vice versa. However, you may use is export on a method definition to make it available also as a multi sub. As with indirect object syntax, the first argument is still always the invocant, but the export allows you to use a comma after the invocant instead of a colon, or to omit the colon entirely in the case of a method with no arguments other than the invocant. Many standard methods (such as IO::close and Array::push) are automatically exported to the CORE namespace by default. For other exported methods, you will not see the multi sub definition unless you use the class in your scope, which will import the multi sub lexically, after which you can call it using normal subroutine call syntax.
In the absence of an explicit type on the method's invocant, the exported multi sub's first argument is implicitly constrained to match the class in which it was defined or composed, so for instance the multi version of close requires its first argument to be of type IO or one of its subclasses. If the invocant is explicitly typed, that will govern the type coverage of the corresponding multi's first argument, whether that is more specific or more general than the class's invocant would naturally be. (But be aware that if it's more specific than ::?CLASS, the binding may reject an otherwise valid single dispatch as well as a multi dispatch.) In any case, it does no good to overgeneralize the invocant if the routine itself cannot handle the broader type. In such a situation you must write a wrapper to coerce to the narrower type.
Note that explicit use of a syntactic category as a method name overrides the choice of dispatcher, so
$x.infix:<*>($y)
and
infix:<*>($x,$y)
are exactly equivalent. That is, both calls use the subroutine/multi dispatcher, not the method/single dispatcher. Likewise
foo($bar)
can be written
$bar.prefix:<foo>()
with the same meaning. To get single dispatch of that method name to a foreign function, you must say:
$bar.'prefix:<foo>'()
Most foreign languages are not going to understand such a method name, however.
From t/spec/S12-methods/multi.t lines 8–200 (no results): (skip)
| # L<S12/"Multi dispatch">
|
|
|
| class Foo {
|
| multi method bar() {
|
| return "Foo.bar() called with no args";
|
| }
|
|
|
| multi method bar(Str $str) {
|
| return "Foo.bar() called with Str : $str";
|
| }
|
|
|
| multi method bar(Int $int) {
|
| return "Foo.bar() called with Int : $int";
|
| }
|
|
|
| multi method bar(Num $num) {
|
| return "Foo.bar() called with Num : $num";
|
| }
|
|
|
| multi method baz($f) {
|
| return "Foo.baz() called with parm : $f";
|
| }
|
| }
|
|
|
|
|
| my $foo = Foo.new();
|
| is($foo.bar(), 'Foo.bar() called with no args', '... multi-method dispatched on no args');
|
|
|
| is($foo.bar("Hello"), 'Foo.bar() called with Str : Hello', '... multi-method dispatched on Str');
|
|
|
| is($foo.bar(5), 'Foo.bar() called with Int : 5', '... multi-method dispatched on Int');
|
| my $num = '4';
|
| is($foo.bar(+$num), 'Foo.bar() called with Num : 4', '... multi-method dispatched on Num');
|
|
|
| #?rakudo todo 'RT #66006'
|
| eval '$foo.baz()';
|
| ok ~$! ~~ /:i argument[s?]/, 'Call with wrong number of args should complain about args';
|
|
|
| role R1 {
|
| method foo($x) { 1 }
|
| }
|
| role R2 {
|
| method foo($x, $y) { 2 }
|
| }
|
| eval_dies_ok 'class X does R1 does R2 { }', 'sanity: get composition conflict error';
|
| class C does R1 does R2 {
|
| proto method foo() { "proto" }
|
| }
|
| my $obj = C.new;
|
| is($obj.foo(), 'proto', 'proto caused methods from roles to be composed without conflict');
|
| is($obj.foo('a'), 1, 'method composed into multi from role called');
|
| is($obj.foo('a','b'), 2, 'method composed into multi from role called');
|
|
|
|
|
| class Foo2 {
|
| multi method a($d) {
|
| "Any-method in Foo";
|
| }
|
| }
|
| class Bar is Foo2 {
|
| multi method a(Int $d) {
|
| "Int-method in Bar";
|
| }
|
| }
|
|
|
| is Bar.new.a("not an Int"), 'Any-method in Foo';
|
|
|
| # RT #67024
|
| #?rakudo todo 'redefintion of non-multi method (RT #67024)'
|
| {
|
| eval 'class A { method a(){0}; method a($x){1} }';
|
| ok $! ~~ Exception, 'redefintion of non-multi method (RT 67024)';
|
| ok "$!" ~~ /multi/, 'error message mentions multi-ness';
|
| }
|
|
|
| {
|
| role R3 {
|
| has @.order;
|
| multi method b() { @.order.push( 'role' ) }
|
| }
|
| class C3 does R3 {
|
| multi method b() { @.order.push( 'class' ); nextsame }
|
| }
|
|
|
| my $c = C3.new;
|
| lives_ok { $c.b }, 'can call multi-method from class with role';
|
|
|
| is $c.order, <class role>, 'call order is correct for class and role'
|
| }
|
|
|
| {
|
| role R4 {
|
| has @.order;
|
| multi method b() { @.order.push( 'role' ); nextsame }
|
| }
|
| class P4 {
|
| method b() { @.order.push( 'parent' ) }
|
| }
|
| class C4 is P4 does R4 {
|
| multi method b() { @.order.push( 'class' ); nextsame }
|
| }
|
| my $c = C4.new;
|
| lives_ok { $c.b }, 'call multi-method from class with parent and role';
|
|
|
| is $c.order, <class role parent>,
|
| 'call order is correct for class, role, parent'
|
| }
|
|
|
| # RT 69192
|
| {
|
| role R5 {
|
| multi method rt69192() { push @.order, 'empty' }
|
| multi method rt69192(Str $a) { push @.order, 'Str' }
|
| }
|
| role R6 {
|
| multi method rt69192(Num $a) { push @.order, 'Num' }
|
| }
|
| class RT69192 { has @.order }
|
|
|
| {
|
| my RT69192 $bot .= new();
|
| ($bot does R5) does R6;
|
| $bot.*rt69192;
|
| is $bot.order, <empty>, 'multi method called once on empty signature';
|
| }
|
|
|
| {
|
| my RT69192 $bot .= new();
|
| ($bot does R5) does R6;
|
| $bot.*rt69192('RT #69192');
|
| is $bot.order, <Str>, 'multi method called once on Str signature';
|
| }
|
|
|
| {
|
| my RT69192 $bot .= new();
|
| ($bot does R5) does R6;
|
| $bot.*rt69192( 69192 );
|
| is $bot.order, <Num>, 'multi method called once on Num signature';
|
| }
|
| }
|
|
|
| {
|
| role RoleS {
|
| multi method d( Str $x ) { 'string' }
|
| }
|
| role RoleI {
|
| multi method d( Int $x ) { 'integer' }
|
| }
|
| class M does RoleS does RoleI {
|
| multi method d( Any $x ) { 'any' }
|
| }
|
|
|
| my M $m .= new;
|
|
|
| is $m.d( 876 ), 'integer', 'dispatch to one role';
|
| is $m.d( '7' ), 'string', 'dispatch to other role';
|
| is $m.d( 1.2 ), 'any', 'dispatch to the class with the roles';
|
|
|
| my @multi_method = $m.^methods.grep({ ~$_ eq 'd' });
|
| is @multi_method.elems, 1, '.^methods returns one element for a multi';
|
|
|
| my $routine = @multi_method[0];
|
| #?rakudo todo 'multi method appears as Routine per r27045'
|
| ok $routine ~~ Routine, 'multi method from ^methods is a Routine';
|
| my @candies = $routine.candidates;
|
| is @candies.elems, 3, 'got three candidates for multi method';
|
|
|
| ok @candies[0] ~~ Method, 'candidate 0 is a method';
|
| ok @candies[1] ~~ Method, 'candidate 1 is a method';
|
| ok @candies[2] ~~ Method, 'candidate 2 is a method';
|
| }
|
|
|
| {
|
| class BrokenTie {
|
| multi method has_tie(Int $x) { 'tie1' };
|
| multi method has_tie(Int $y) { 'tie2' };
|
| }
|
|
|
| #?rakudo todo 'ambiguous dispatch should die'
|
| dies_ok { BrokenTie.has_tie( 42 ) }, 'call to tied method dies';
|
|
|
| class WorkingTie is BrokenTie {
|
| multi method has_tie(Int $z) { 'tie3' };
|
| multi method has_tie(Str $s) { 'tie4' };
|
| }
|
|
|
| is WorkingTie.has_tie( 42 ), 'tie3', 'broken class fixed by subclass (1)';
|
| is WorkingTie.has_tie( 'x' ), 'tie4', 'broken class fixed by subclass (2)';
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
Multi submethods work just like multi methods except they are constrained to an exact type match on the invocant, just as ordinary submethods are.
Perl 6.0.0 is not required to support multiple dispatch on named parameters, only on positional parameters. Note that most builtins will map known named parameters to positional via a proto declaration.
Within a multiple dispatch, nextsame means to try the next best match, or next best default in case of tie, or the proto sub if there is one.
Attributes are tied to a particular class definition, so a multi method can only directly access the attributes of a class it's defined within when the invocant is the "self" of that attribute. However, it may call the private attribute accessors from a different class if that other class has indicated that it trusts the class the multi method is defined in:
From t/spec/S12-methods/trusts.t lines 6–124 (no results): (skip)
| # L<S12/Multi dispatch/"if that other class has indicated that it trusts the
|
| # class">
|
|
|
| plan 15;
|
|
|
| class A {
|
| trusts B;
|
|
|
| has $!foo;
|
| has @!bar;
|
| has %!baz;
|
| }
|
|
|
| class B {
|
| has A $!my_A;
|
|
|
| submethod BUILD () {
|
| my $an_A = A.new();
|
|
|
| try {
|
| $an_A!A::foo = 'hello';
|
| };
|
| is( $!.defined ?? 1 !! 0, 0, 'A trusts B, B can set an A scalar attr; '~($!//'') );
|
|
|
| try {
|
| $an_A!A::bar = [1,2,3];
|
| };
|
| is( $!.defined ?? 1 !! 0, 0, 'A trusts B, B can set an A array attr; '~($!//'') );
|
|
|
| try {
|
| $an_A!baz = {'m'=>'v'};
|
| };
|
| is( $!.defined ?? 1 !! 0, 0, 'A trusts B, B can set an A hash attr; '~($!//'') );
|
|
|
| $!my_A = $an_A;
|
| }
|
|
|
| method read_from_A() {
|
| my ($foo, @bar, %baz);
|
| my $an_A = $!my_A;
|
|
|
| try {
|
| $foo = $!an_A!A::foo;
|
| };
|
| #?pugs 2 todo 'feature'
|
| is( $!.defined ?? 1 !! 0, 0, 'A trusts B, B can get an A scalar attr; '~($!//''));
|
| is( $foo, 'hello', 'value read by B from an A scalar var is correct');
|
|
|
| try {
|
| @bar = $!an_A!A::bar;
|
| };
|
| #?pugs 2 todo 'feature'
|
| is( $!.defined ?? 1 !! 0, 0, 'A trusts B, B can get an A array attr; '~($!//''));
|
| is_deeply( @bar, [1,2,3], 'value read by B from an A scalar var is correct');
|
|
|
| try {
|
| %baz = $!an_A!A::baz;
|
| };
|
| #?pugs 2 todo 'feature'
|
| is( $!.defined ?? 1 !! 0, 0, 'A trusts B, B can get an A hash attr; '~($!//'') );
|
| is_deeply( %baz, {'m'=>'v'}, 'value read by B from an A scalar var is correct' );
|
| }
|
| }
|
|
|
| class C {
|
| has A $!my_A;
|
|
|
| submethod BUILD () {
|
| my $an_A = A.new();
|
|
|
| try {
|
| $an_A!A::foo = 'hello';
|
| };
|
| #?pugs todo 'feature'
|
| is( $!.defined ?? 1 !! 0, 1, 'A does not trust C, C can not set an A scalar attr; '~($!//'') );
|
|
|
| try {
|
| $an_A!A::bar = [1,2,3];
|
| };
|
| #?pugs todo 'feature'
|
| is( $!.defined ?? 1 !! 0, 1, 'A does not trust C, C can not set an A array attr; '~($!//'') );
|
|
|
| try {
|
| $an_A!A::baz = {'m'=>'v'};
|
| };
|
| #?pugs todo 'feature'
|
| is( $!.defined ?? 1 !! 0, 1, 'A does not trust C, C can not set an A hash attr; '~($!//'') );
|
|
|
| $!my_A = $an_A;
|
| }
|
|
|
| method read_from_A() {
|
| my ($foo, @bar, %baz);
|
| my $an_A = $!my_A;
|
|
|
| try {
|
| $foo = $an_A!A::foo;
|
| };
|
| is( $!.defined ?? 1 !! 0, 1, 'A does not trust C, C can not get an A scalar attr; '~($!//'') );
|
|
|
| try {
|
| @bar = $an_A!A::bar;
|
| };
|
| is( $!.defined ?? 1 !! 0, 1, 'A does not trust C, C can not get an A array attr; '~($!//'') );
|
|
|
| try {
|
| %baz = $an_A!A::baz;
|
| };
|
| is( $!.defined ?? 1 !! 0, 1, 'A does not trust C, C can not get an A hash attr; '~($!//'') );
|
| }
|
| }
|
|
|
| my $my_B = B.new();
|
| $my_B.read_from_A();
|
|
|
| my $my_C = C.new();
|
| $my_C.read_from_A();
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
class MyClass {
trusts YourClass;
...
}
The syntax for calling back to MyClass is $obj!MyClass::meth(). Note that private attribute accessors are always invoked directly, never via a dispatcher, since there is never any question about which object is being referred to. Hence, the private accessor notation may be aggressively inlined for simple attributes, and no simpler notation is needed for accessing another object's private attributes.
The sub keyword is optional immediately after a proto, multi, or only keyword.
A proto declaration may not occur after a multi declaration in the same scope.
From t/spec/S12-attributes/delegation.t lines 13–40 (no results): (skip)
| # L<S12/Delegation>
|
|
|
| class Backend1 { method hi() { 42 }; method cool() { 1337 } }
|
| class Backend2 { method hi() { 23 }; method cool() { 539 } }
|
| class Frontend { has $.backend is rw handles "hi" }
|
| ok Backend1.new, "class definition worked";
|
|
|
| is Backend1.new.hi, 42, "basic sanity (1)";
|
| is Backend2.new.hi, 23, "basic sanity (2)";
|
|
|
| {
|
| my $a;
|
| ok ($a = Frontend.new), "basic instantiation worked (1)";
|
| dies_ok { $a.hi }, "calling a method on no object didn't succeed (1)";
|
| ok ($a.backend = Backend1.new()), "setting a handler object (1)";
|
| ok (!($a ~~ Backend1)), "object wasn't isa()ed (1)";
|
| is $a.hi, 42, "method was successfully handled by backend object (1)";
|
| }
|
|
|
| {
|
| my $a;
|
| ok ($a = Frontend.new), "basic instantiation worked (2)";
|
| dies_ok { $a.hi }, "calling a method on no object didn't succeed (2)";
|
| ok ($a.backend = Backend2.new()), "setting a handler object (2)";
|
| ok (!($a ~~ Backend2)), "object wasn't isa()ed (2)";
|
| is $a.hi, 23, "method was successfully handled by backend object (2)";
|
| }
|
|
|
Highlighted:
small|full
Delegation lets you pretend that some other object's methods are your own. Delegation is specified by a handles trait verb with an argument specifying one or more method names that the current object and the delegated object will have in common:
has $tail handles 'wag';
Since the method name (but nothing else) is known at class construction time, the following .wag method is autogenerated for you:
method wag (|$args) { $!tail.wag(|$args) }
You can specify multiple method names:
From t/spec/S12-attributes/delegation.t lines 41–54 (no results): (skip)
| # L<S12/Delegation/You can specify multiple method names:>
|
| class MultiFrontend { has $.backend is rw handles <hi cool> }
|
| ok MultiFrontend.new, "class definition using multiple method names worked";
|
| {
|
| my $a;
|
| ok ($a = MultiFrontend.new), "basic instantiation worked (5)";
|
| dies_ok { $a.hi }, "calling a method on no object didn't succeed (5-1)";
|
| dies_ok { $a.cool }, "calling a method on no object didn't succeed (5-2)";
|
| ok ($a.backend = Backend1.new()), "setting a handler object (5)";
|
| ok (!($a ~~ Backend1)), "object wasn't isa()ed (5)";
|
| is ($a.hi), 42, "method was successfully handled by backend object (5-1)";
|
| is ($a.cool), 1337, "method was successfully handled by backend object (5-2)";
|
| }
|
|
|
Highlighted:
small|full
has $.legs handles <walk run lope shake lift>;
It's illegal to call the outer method unless the attribute has been initialized to an object of a type supporting the method, such as by:
has Tail $.tail handles 'wag' .= new(|%_);
Note that putting a Tail type on the attribute does not necessarily mean that the method is always delegated to the Tail class. The dispatch is still based on the run-time type of the object, not the declared type.
Any other kind of argument to handles is considered to be a smartmatch selector for method names. So you can say:
From t/spec/S12-attributes/delegation.t lines 97–176 (no results): (skip)
| # L<S12/Delegation/"Any other kind of argument" "smartmatch selector for method">
|
| {
|
| class ReFrontend { has $.backend is rw handles /^hi|oo/ };
|
| ok ReFrontend.new, "class definition using a smartmatch handle worked";
|
|
|
| {
|
| my $a;
|
| ok ($a = ReFrontend.new), "basic instantiation worked (3)";
|
| dies_ok { $a.hi }, "calling a method on no object didn't succeed (3)";
|
| ok ($a.backend = Backend1.new()), "setting a handler object (3)";
|
| ok (!($a ~~ Backend1)), "object wasn't isa()ed (3)";
|
| #?pugs skip 'feature'
|
| is $a.hi, 42, "method was successfully handled by backend object (3)";
|
| is $a.cool, 1337, "method was successfully handled by backend object (3)";
|
| }
|
| }
|
| {
|
| class WorrevaFrontend {
|
| has $.backend is rw handles *;
|
| has $.backend2 is rw handles *;
|
| method test { 1 }
|
| method hi { 2 }
|
| }
|
| ok WorrevaFrontend.new, "class definition using a smartmatch on * worked";
|
| my $a = WorrevaFrontend.new();
|
| $a.backend = Backend1.new();
|
| $a.backend2 = Backend2.new();
|
| is $a.test, 1, "didn't try to delegate method in the class even with handles *...";
|
| is $a.hi, 2, "...even when it exists in the target class";
|
| is $a.cool, 1337, "...but otherwise it delegates, and first * wins";
|
| }
|
|
|
| # delegation with lvalue routines
|
| {
|
| class BackendRw {
|
| has $.a is rw;
|
| has $.b is rw;
|
| has $.c;
|
| }
|
| class FrontendRw {
|
| has BackendRw $.backend handles <a b c>;
|
| submethod BUILD {
|
| $!backend = BackendRw.new();
|
| }
|
| }
|
| my $t = FrontendRw.new();
|
| lives_ok { $t.a = 'foo' }, 'can assign to lvalue delegated attribute';
|
| dies_ok { $t.c = 'foo' }, '... but only to lvaues attributes';
|
| is $t.a, 'foo', 'assignment worked';
|
| is $t.backend.a, 'foo', 'can also query that through the backend';
|
| ok $t.c.notdef, 'died assignment had no effect';
|
| }
|
|
|
| # arrays, hashes
|
| {
|
| class PseudoArray {
|
| has @!data handles <Str push pop elems shift unshift>;
|
| }
|
| my $x = PseudoArray.new();
|
| $x.push: 3, 4;
|
| $x.push: 6;
|
| is ~$x, '3 4 6', 'delegation of .Str and .push to array attribute';
|
| $x.pop;
|
| is ~$x, '3 4', 'delegation of .pop';
|
| $x.unshift('foo');
|
| is ~$x, 'foo 3 4', 'delegation of .unshift';
|
| is $x.shift, 'foo', 'delegation of .shift (1)';
|
| is ~$x, '3 4', 'delegation of .shift (2)';
|
| is $x.elems, 2, 'delegation of .elems';
|
| }
|
| {
|
| class PseudoHash { has %!data handles <push Str> };
|
| my $h = PseudoHash.new;
|
| $h.push: 'a' => 5;
|
| is $h.Str, ~{a => 5}, 'delegation of .Str and .push to hash';
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: syn=perl6
|
Highlighted:
small|full
has $.fur is rw handles /^get_/;
If you say
From t/spec/S12-attributes/delegation.t lines 71–96 (no results): (skip)
| # L<S12/Delegation/If you say>
|
| {
|
| class ClassFrontend { has $.backend is rw handles Backend2 };
|
| ok ClassFrontend.new, "class definition using a Class handle worked";
|
| {
|
| my $a;
|
| ok ($a = ClassFrontend.new), "basic instantiation worked (4)";
|
| dies_ok { $a.hi }, "calling a method on no object didn't succeed (4)";
|
| ok ($a.backend = Backend1.new()), "setting a handler object (4)";
|
| ok (!($a ~~ Backend1)), "object wasn't isa()ed (4-1)";
|
| ok (!($a ~~ Backend2)), "object wasn't isa()ed (4-2)";
|
| is $a.hi, 42, "method was successfully handled by backend object (4)";
|
| }
|
| }
|
| {
|
| role R1 { method awesome { "yeah!" } }
|
| class Backend3 does R1 { method sucks { "boo" } }
|
| class RoleFrontend { has $.backend is rw handles R1; }
|
| my $a = RoleFrontend.new();
|
| ok !$a.does(R1), "having a handles role doesn't make the class do the role";
|
| dies_ok { $a.awesome }, "calling a method on no object didn't succeed";
|
| $a.backend = Backend3.new();
|
| is $a.awesome, "yeah!", "method in role was successfully handled by backend object";
|
| dies_ok { $a.sucks }, "but method in backend class but not role not handled";
|
| }
|
|
|
Highlighted:
small|full
has $.fur is rw handles Groomable;
then you get only those methods available via the Groomable role or class. To delegate everything, use the Whatever matcher:
has $the_real_me handles *;
Wildcard matches are evaluated only after it has been determined that there's no exact match to the method name anywhere. When you have multiple wildcard delegations to different objects, it's possible to have a conflict of method names. Wildcard method matches are evaluated in order, so the earliest one wins. (Non-wildcard method conflicts can be caught at class composition time.)
If, where you would ordinarily specify a string, you put a pair, then the pair maps the method name in this class to the method name in the other class. If you put a hash, each key/value pair is treated as such a mapping. Such mappings are not considered wildcards.
From t/spec/S12-attributes/delegation.t lines 55–70 (no results): (skip)
| # L<S12/Delegation/you put a pair>
|
| class PairTest {
|
| has $.backend1 is rw handles :hello<hi>;
|
| has $.backend2 is rw handles (:ahoj<hi>, :w00t('cool'));
|
| }
|
| {
|
| my $a = PairTest.new;
|
| $a.backend1 = Backend1.new();
|
| $a.backend2 = Backend2.new();
|
| dies_ok { $a.hi }, "calling method with original name fails";
|
| dies_ok { $a.cool }, "calling method with original name fails";
|
| is $a.hello, 42, "calling method with mapped name works";
|
| is $a.ahoj, 23, "calling method with mapped name works";
|
| is $a.w00t, 539, "calling method with mapped name works";
|
| }
|
|
|
Highlighted:
small|full
has $.fur handles { :shakefur<shake>, :scratch<get_fleas> };
You can do a wildcard renaming, but not with pairs. Instead do smartmatch with a substitution:
has $.fur handles (s/^furget_/get_/);
Ordinarily delegation is based on an attribute holding an object, but it can also be based on the return value of a method:
method select_tail handles <wag hang> {...}
From t/spec/S12-subset/multi-dispatch.t lines 4–30 (no results): (skip)
| # L<S12/Types and Subtypes/>
|
|
|
| plan 6;
|
|
|
| subset Even of Int where { $_ % 2 == 0 };
|
| subset Odd of Int where { $_ % 2 == 1 };
|
|
|
| multi sub test_subtypes(Even $y){ 'Even' }
|
| multi sub test_subtypes(Odd $y){ 'Odd' }
|
|
|
| is test_subtypes(3), 'Odd', 'mutli dispatch with type mutual exclusive type constaints 1';
|
| is test_subtypes(4), 'Even', 'mutli dispatch with type mutual exclusive type constaints 1';
|
|
|
|
|
| multi sub mmd(Even $x) { 'Even' }
|
| multi sub mmd(Int $x) { 'Odd' }
|
|
|
| is mmd(3), 'Odd' , 'MMD with subset type multi works';
|
| is mmd(4), 'Even', 'subset multi is narrower than the general type';
|
|
|
|
|
| proto foo ($any) { ":)" }
|
| multi foo ($foo where { $_ eq "foo"}) { $foo }
|
| is foo("foo"), "foo", "when we have a single candidate with a constraint, it's enforced";
|
| is foo("bar"), ":)", "proto called when single constraint causes failed dispatch";
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
From t/spec/S12-subset/subtypes.t lines 13–184 (no results): (skip)
| # L<S12/"Types and Subtypes">
|
|
|
| my $abs = '
|
| multi sub my_abs (Num $n where { $^n >= 0 }){ $n }
|
| multi sub my_abs (Num $n where { $^n < 0 }){ -$n }
|
| ';
|
|
|
| ok(eval("$abs; 1"), "we can compile subtype declarations");
|
|
|
| is(eval("my_abs(3)"), 3, "and we can use them, too");
|
| is(eval("my_abs(-5)"), 5, "and they actually work");
|
|
|
| # another nice example
|
| {
|
| multi factorial (Int $x) { $x * factorial($x-1) };
|
| multi factorial (Int $x where 0 ) { 1 };
|
| is factorial(3), 6, 'subset types refine candidate matches';
|
| }
|
|
|
| # Basic subtype creation
|
| ok eval('subset Num::Odd of Num where { $^num % 2 == 1 }; 1'),
|
| "subtype is correctly parsed";
|
| is eval('my Num::Odd $a = 3'), 3, "3 is an odd num";
|
| # The eval inside the eval is/will be necessary to hider our smarty
|
| # compiler's compile-time from bailing.
|
| # (Actually, if the compiler is *really* smarty, it will notice our eval trick,
|
| # too :))
|
| is eval('my Num::Odd $b = 3; try { $b = eval "4" }; $b'), 3,
|
| "objects of Num::Odd don't get even";
|
|
|
| # Subtypes should be undefined.
|
| is eval('Num::Odd.defined'), 0, 'subtypes are undefined';
|
|
|
| # The same, but lexically
|
| my $eval1 = '{
|
| my subset Num::Even of Num where { $^num % 2 == 0 }
|
| ok my Num::Even $c = 6;
|
| ok $c ~~ Num::Even, "our var is a Num::Even";
|
| try { $c = eval 7 }
|
| is $c, 6, "setting a Num::Even to an odd value dies";
|
| }';
|
| eval($eval1) // skip 3, 'Cant parse';
|
| #?rakudo todo 'lexical subtypes'
|
| ok eval('!try { my Num::Even $d }'),
|
| "lexically declared subtype went out of scope";
|
|
|
| # Subs with arguments of a subtype
|
| ok eval('sub only_accepts_odds(Num::Odd $odd) { $odd + 1 }'),
|
| "sub requiring a Num::Odd as argument defined (1)";
|
| is eval('only_accepts_odds(3)'), 4, "calling sub worked";
|
| #?rakudo skip 'return value of try on a failure is null'
|
| ok eval('!try { only_accepts_odds(4) }'), "calling sub did not work";
|
|
|
| # Normal Ints automatically morphed to Num::Odd
|
| ok eval('sub is_num_odd(Num::Odd $odd) { $odd ~~ Num::Odd }'),
|
| "sub requiring a Num::Odd as argument defined (2)";
|
| ok eval('is_num_odd(3)'), "Int accepted by Num::Odd";
|
|
|
| # Following code is evil, but should work:
|
| {
|
| my Int $multiple_of;
|
| subset Num::Multiple of Int where { $^num % $multiple_of == 0 }
|
|
|
| $multiple_of = 5;
|
| ok $multiple_of ~~ Int, "basic sanity (1)";
|
| is $multiple_of, 5, "basic sanity (2)";
|
|
|
| ok (my Num::Multiple $d = 10), "creating a new Num::Multiple";
|
| is $d, 10, "creating a new Num::Multiple actually worked";
|
| dies_ok { $d = 7 }, 'negative test also works';
|
| is $d, 10, 'variable kept previous value';
|
|
|
|
|
| $multiple_of = 6;
|
| dies_ok { my Num::Multiple $e = 10 }, "changed subtype definition worked";
|
| }
|
|
|
| # Rakudo had a bug where 'where /regex/' failed
|
| # http://rt.perl.org/rt3/Ticket/Display.html?id=60976
|
| #?DOES 2
|
| {
|
| subset HasA of Str where /a/;
|
| lives_ok { my HasA $x = 'bla' }, 'where /regex/ works (positive)';
|
| eval_dies_ok 'my HasA $x = "foo"', 'where /regex/ works (negative)';
|
| }
|
|
|
| # You can write just an expression rather than a block after where in a sub
|
| # and it will smart-match it.
|
| {
|
| sub anon_where_1($x where "x") { 1 }
|
| sub anon_where_2($x where /x/) { 1 }
|
| is(anon_where_1('x'), 1, 'where works with smart-matching on string');
|
| dies_ok({ anon_where_1('y') }, 'where works with smart-matching on string');
|
| is(anon_where_2('x'), 1, 'where works with smart-matching on regex');
|
| is(anon_where_2('xyz'), 1, 'where works with smart-matching on regex');
|
| dies_ok({ anon_where_2('y') }, 'where works with smart-matching on regex');
|
| }
|
|
|
| # Block parameter to smart-match is readonly.
|
| {
|
| subset SoWrong of Str where { $^epic = "fail" }
|
| sub so_wrong_too($x where { $^epic = "fail" }) { }
|
| my SoWrong $x;
|
| dies_ok({ $x = 42 }, 'parameter in subtype is read-only...');
|
| dies_ok({ so_wrong_too(42) }, '...even in anonymous ones.');
|
| }
|
|
|
| # ensure that various operations do type cheks
|
|
|
| {
|
| subset AnotherEven of Int where { $_ % 2 == 0 };
|
| my AnotherEven $x = 2;
|
| dies_ok { $x++ }, 'Even $x can not be ++ed';
|
| is $x, 2, '..and the value was preserved';
|
| dies_ok { $x-- }, 'Even $x can not be --ed';
|
| is $x, 2, 'and the value was preserved';
|
| }
|
|
|
| {
|
| # chained subset types
|
| subset Positive of Int where { $_ > 0 };
|
| subset NotTooLarge of Positive where { $_ < 10 };
|
|
|
| my NotTooLarge $x;
|
|
|
| lives_ok { $x = 5 }, 'can satisfy both conditions on chained subset types';
|
| dies_ok { $x = -2 }, 'violating first condition barfs';
|
| dies_ok { $x = 22 }, 'violating second condition barfs';
|
| }
|
|
|
|
|
| # subtypes based on user defined classes and roles
|
| {
|
| class C1 { has $.a }
|
| subset SC1 of C1 where { .a == 42 }
|
| ok !(C1.new(a => 1) ~~ SC1), 'subtypes based on classes work';
|
| ok C1.new(a => 42) ~~ SC1, 'subtypes based on classes work';
|
| }
|
| {
|
| role R1 { };
|
| subset SR1 of R1 where 1;
|
| ok !(1 ~~ SR1), 'subtypes based on roles work';
|
| my $x = 1 but R1;
|
| ok $x ~~ SR1, 'subtypes based on roles work';
|
| }
|
|
|
| subset NW1 of Int;
|
| ok NW1 ~~ Int, 'subset declaration without where clause does type it refines';
|
| ok 0 ~~ NW1, 'subset declaration without where clause accepts right value';
|
| ok 42 ~~ NW1, 'subset declaration without where clause accepts right value';
|
| ok 4.2 !~~ NW1, 'subset declaration without where clause rejects wrong value';
|
| ok "x" !~~ NW1, 'subset declaration without where clause rejects wrong value';
|
|
|
| # RT #65700
|
| {
|
| subset Small of Int where { $^n < 10 }
|
| class RT65700 {
|
| has Small $.small;
|
| }
|
| dies_ok { RT65700.new( small => 20 ) }, 'subset type is enforced as attribute in new() (1)';
|
| lives_ok { RT65700.new( small => 2 ) }, 'subset type enforced as attribute in new() (2)';
|
|
|
| my subset Teeny of Int where { $^n < 10 }
|
| class T { has Teeny $.teeny }
|
| #?rakudo 2 todo 'RT 65700'
|
| dies_ok { T.new( small => 20 ) }, 'my subset type is enforced as attribute in new() (1)';
|
| lives_ok { T.new( small => 2 ) }, 'my subset type enforced as attribute in new() (2)';
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
The type system of Perl consists of roles, classes, and subtypes. You can declare a subtype like this:
my subset Str_not2b of Str where /^[isnt|arent|amnot|aint]$/;
or this:
my Str subset Str_not2b where /^[isnt|arent|amnot|aint]$/;
An anonymous subtype looks like this:
Str where /^[isnt|arent|amnot|aint]$/;
A where clause implies future smartmatching of some kind: the as-yet unspecified object of the type on the left must match the selector on the right. Our example is roughly equivalent to this closure:
{ $_.does(Str) and $_ ~~ /^[isnt|arent|amnot|aint]$/; }
except that a subtype knows when to call itself.
A subtype is not a subclass. Subclasses add capabilities, whereas a subtype adds constraints (takes away capabilites). A subtype is primarily a handy way of sneaking smartmatching into multiple dispatch. Just as a role allows you to specify something more general than a class, a subtype allows you to specify something more specific than a class. A subtype specifies a subset of the values that the original type specified, which is why we use the subset keyword for it.
While subtypes are primarily intended for restricting parameter types for multiple dispatch, they also let you impose preconditions on assignment. If you declare any container with a subtype, Perl will check the constraint against any value you might try to bind or assign to the container.
subset Str_not2b of Str where /^[isnt|arent|amnot|aint]$/;
subset EvenNum of Num where { $^n % 2 == 0 }
my Str_not2b $hamlet;
$hamlet = 'isnt'; # Okay because 'isnt' ~~ /^[isnt|arent|amnot|aint]$/
$hamlet = 'amnt'; # Bzzzzzzzt! 'amnt' !~~ /^[isnt|arent|amnot|aint]$/
my EvenNum $n;
$n = 2; # Okay
$n = -2; # Okay
$n = 0; # Okay
$n = 3; # Bzzzzzzzt
It's legal to base one subtype on another; it just adds an additional constraint. That is, it's a subset of a subset.
You can use an anonymous subtype in a signature:
sub check_even (Num where { $^n % 2 == 0 } $even) {...}
That's a bit unwieldy, but by the normal type declaration rules you can turn it around to get the variable out front:
sub check_even ($even of Num where { $^n % 2 == 0 }) {...}
and just for convenience we also let you write it:
sub check_even (Num $even where { $^n % 2 == 0 }) {...}
since all the type constraints in a signature parameter are just anded together anyway.
You can leave out the block when matching against a literal value of some kind:
multi sub fib (Int $n where 0|1) { return $n }
multi sub fib (Int $n) { return fib($n-1) + fib($n-2) }
In fact, you can leave out the 'where' declaration altogether:
multi sub fib (0) { return 0 }
multi sub fib (1) { return 1 }
multi sub fib (Int $n) { return fib($n-1) + fib($n-2) }
Subtype constraints are used as tiebreakers in multiple dispatch:
use Rules::Common :profanity;
multi sub mesg ($mesg of Str where /<profanity>/ is copy) {
$mesg ~~ s:g/<profanity>/[expletive deleted]/;
print $MESG_LOG: $mesg;
}
multi sub mesg ($mesg of Str) {
print $MESG_LOG: $mesg;
}
For multi dispatch, a long name with a matching constraint is preferred over an equivalent one with no constraint. So the first mesg above is preferred if the constraint matches, and otherwise the second is preferred.
To export a subset type, put the export trait just before the where:
subset Positive of Int is export where * > 0;
For any named type, certain other subset types may automatically be derived from it by appending an appropriate adverbial to its name:
Int:_ Allow either defined or undefined Int values
Int:U Allow only undefined (abstract) Int values
Int:D Allow only defined (concrete) Int values
That is, these are equivalent:
Int:U Int:_ where !*.defined
Int:D Int:_ where *.defined
In standard Perl 6, Int is always assumed to mean Int:_, but this default may be changed within a lexical scope by various pragmas. In particular,
use parameters :D;
will cause non-invocant parameters to default to :D, while
use invocant :D;
will do the same for the invocant parameter. In such lexical scopes you may use the :_ form to get back to the standard behavior. Conjecturally,
use variables :D;
would do the same for types used in variable declarations.
[Conjecture: This entire section is considered a guess at our post-6.0.0 direction. For 6.0.0 we will allow only a single constraint before the variable, and post constraints will all be considered "epsilon" narrower than the single type on the left. The single constraint on the left may, however, be a value like 0 or a named subset type. Such a named subset type may be predeclared with an arbitrarily complex where clause; for 6.0.0 any structure type information inferrable from the where clause will be ignored, and the declared subset type will simply be considered nominally derived from the of type mentioned in the same declaration.]
More generally, a parameter can have a set of constraints, and the set of constraints defines the formal type of the parameter, as visible to the signature. (No one constraint is priviledged as the storage type of the actual argument, unless it is a native type.) All constraints considered in type narrowness. That is, these are equivalently narrow:
Foo Bar @x
Bar Foo @x
The constraint implied by the sigil also counts as part of the official type. The sigil is actually a constraint on the container, so the actual type of the parameter above is something like:
Positional[subset :: of Any where Foo & Bar }]
Static where clauses also count as part of the official type. A where clause is considered static if it can be applied to the types to the left of it at compile time to produce a known finite set of values. For instance, a subset of an enum type is a static set of values. Hence
Day $d where 'Mon'..'Fri'
is considered equivalent to
subset Weekday of Day where 'Mon'..'Fri';
Weekday $d
Types mentioned in a dynamic where class are not considered part of the official type, except insofar as the type includes the notion: "is also constrained by a dynamic where clause", which narrows it by epsilon over the equivalent type without a where clause.
Foo Bar @x # type is Foo & Bar & Positional
Foo Bar @x where Baz # slightly tighter than Foo Bar Positional
The set of constraints for a parameter creates a subset type that implies some set of allowed values for the parameter. The set of allowed values may or may not be determinable at compile time. When the set of allowed values is determinable at compile time, we call it a static subtype.
Type constraints that resolve to a static subtype (that is, with a fixed set of elements knowable (if not known) at compile time) are considered to be narrower than type constraints that involve run-time calculation, or are otherwise intractable at compile time. Note that all values such as 0 or "foo" are considered singleton static subtypes. Singleton values are considered narrower than a subtype with multiple values, even if the subtype contains the value in question. This is because, for enumerable types, type narrowness is defined by doing set theory on the set of enumerated values.
So assuming:
my enum Day ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
subset Weekday of Day where 'Mon' .. 'Fri'; # considered static
subset Today of Day where *.today;
we have the following pecking order:
Parameter # Set of possible values
========= ========================
Int $n # Int
Int $n where Today # Int plus dynamic where
Int $n where 1 <= * <= 5 # Int plus dynamic where
Day $n # 0..6
Day $n where Today # 0..6 plus dynamic where
Day $n where 1 <= * <= 5 # 1..5
Int $n where Weekday # 1..5
Day $n where Weekday # 1..5
Weekday $n # 1..5
Tue # 2
Note the difference between:
Int $n where 1 <= * <= 5 # Int plus dynamic where
Day $n where 1 <= * <= 5 # 1..5
The first where is considered dynamic not because of the nature of the comparisons but because Int is not finitely enumerable. Our Weekday subset type can calculate the set membership at compile time because it is based on the Day enum, and hence is considered static despite the use of a where. Had we based Weekday on Int it would have been considered dynamic. Note, however, that with "anded" constraints, any enum type governs looser types, so
Int Day $n where 1 <= * <= 5
is considered static, since Day is an enum, and cuts down the search space.
The basic principle we're trying to get at is this: in comparing two parameter types, the narrowness is determined by the subset relationships on the sets of possible values, not on the names of constraints, or the method by which those constraints are specified. For practical reasons, we limit our subset knowledge to what can be easily known at compile time, and consider the presence of one or more dynamic constraints to be epsilon narrower than the same set of possible values without a dynamic constraint.
As a first approximation for 6.0.0, subsets of enums are static, and other subsets are dynamic. We may refine this in subsequent versions of Perl.
An enumeration is a type that facilitates the use of a set of symbols to represent a set of constant values. Its most obvious use is the translation of those symbols to their corresponding values. Each enumeration association is a constant pair known as an enum, which is of type Enum. Each enum associates an enum key with an enum value. Semantically therefore, an enumeration operates like a constant hash, but since it uses a package Stash to hold the entries, it presents itself to the user's namespace as a typename package containing a set of constant declarations. That is,
enum E <a b c>;
is largely syntactic sugar for:
package E {
constant a = 0;
constant b = 1;
constant c = 2;
}
(However, the enum declaration supplies extra semantics.)
Such constant declarations allow the use of the declared names to stand in for the values where a value is desired. In addition, since a constant declaration introduces a name that behaves as a subtype matching a single value, the enum key can function as a typename in certain capacities where a typename is required. The name of the enumeration as a whole is also considered a typename, and may be used to represent the set of values. (Note that when we wish to verbally distinguish the enumeration as a whole from each individual enum pair, we use the long term "enumeration" for the former, despite the fact that it is declared using the enum keyword.)
In the enum declaration, the keys are specified as a parenthesized list, or an equivalent angle bracket list:
my enum Day ('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
my enum Day <Sun Mon Tue Wed Thu Fri Sat>;
The values are generated implicitly by default, but may be also be specified explicitly. If the first value is unspecified, it defaults to 0. To specify the first value, use pair notation (see below).
If the declared enumeration typename begins with an uppercase letter, the enum values will be derived from Int or Str as appropriate. If the enumeration typename is lowercase, the enumeration is assumed to be representing a set of native values, so the default value type is int or buf.
The base type can be specified if desired:
my bit enum maybe <no yes>;
my Int enum day ('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
our enum day of uint4 <Sun Mon Tue Wed Thu Fri Sat>;
The declared base type automatically distributes itself to the individual constant values. For non-native types, the enum objects are guaranteed only to be derived from and convertible to the specified type. The actual type of the enum object returned by using the symbol is the enumeration type itself.
Fri.WHAT # Day, not Int.
+Fri # 5
Fri ~~ Int # True, because derived from Int
Fri.perl # 'Day::Fri'
Fri.key # 'Fri'
Fri.defined # True
Other than that, number valued enums act just like numbers, while string valued enums act just like strings. Fri.so is true because its value is 5 rather than 0. Sun.so is false.
Enums based on native types may be used only for their value, since a native value doesn't know its own type.
Since methods on native types delegate to their container's type, a variable typed with a native type will know which method to call:
my day $d = 3;
$d.key # returns "Wed"
Such declarational forms are not always convenient; to translate native enum values back to their names operationally, you can pull out the enum type's EnumMap and invert it:
constant %dayname := Day.enums.invert;
%dayname{3} # Wed
The enumeration type itself is an undefined type object, but supplies convenient methods:
Day.defined # False
3 ~~ Day # True, using Day as a subset of Int
Day.enums # map of key/value pairs
The .enums method returns an EnumMap that may be used either as a constant hash value or as a list of pairs:
my enum CoinFace <Heads Tails>;
CoinFace.enums.keys # ('Heads', 'Tails')
CoinFace.enums.values # (0, 1)
CoinFace.enums.kv # ('Heads', 0, 'Tails', 1)
CoinFace.enums.invert # (0 => 'Heads', 1 => 'Tails')
CoinFace.enums.[1] # Tails => 1
The enumeration typename itself may be used as a coercion operator from either the key name or a value. First the argument is looked up as a key; if that is found, the enum object is returned. If the key name lookup fails, the value is looked up using an inverted mapping table (which might have dups if the mapping is not one-to-one):
Day('Tue') # Tue constant, found as key
Day::('Tue') # (same thing)
Day(3) # Wed constant, found as value
Day.enums.invert{3} # (same thing)
An anonymous enum just makes sure each string turns into a pair with sequentially increasing values, so:
%e = enum < ook! ook. ook? >;
is equivalent to:
%e = ();
%e<ook!> = 0;
%e<ook.> = 1;
%e<ook?> = 2;
The return value of an anonymous enumeration is an EnumMap. The enum keyword is still a declarator here, so the list is evaluated at compile time. Use a coercion to EnumMap to get a run-time map.
The enumeration composer inspects list values for pairs, where the value of the pair sets the next value explicitly. Non-pairs ++ the previous value. (Str and buf types increment like Perl 5 strings.) Since the «...» quoter automatically recognizes pair syntax along with interpolations, we can simply say:
my enum DayOfWeek «:Sun(1) Mon Tue Wed Thu Fri Sat»;
our Str enum Phonetic «:Alpha<A> Bravo Charlie Delta
Echo Foxtrot Golf Hotel India Juliet
Kilo Lima Mike November Oscar Papa
Quebec Romeo Sierra Tango Uniform
Victor Whiskey X-ray Yankee Zulu»;
enum roman (i => 1, v => 5,
x => 10, l => 50,
c => 100, d => 500,
m => 1000);
my Item enum hex «:zero(0) one two three four five six seven eight nine
:ten<a> eleven twelve thirteen fourteen fifteen»;
Note that enumeration declaration evaluates its list at compile time, so any interpolation into such a list may not depend on run-time values. Otherwise enums wouldn't be constants. (If this isn't what you want, try initializing an ordinary declaration using ::= to make a scoped readonly value.)
You may import enum types; only non-colliding symbols are imported. Colliding enum keys are hidden and must be disambiguated with the type name. Any attempt to use the ambiguous name will result in a fatal compilation error. (All colliding values are hidden, not just the new one, or the old one.) Any explicit sub or type definition hides all imported enum keys of the same name but will produce a warning unless is redefined is included.
Since non-native Enum values know their enumeration type, they may be used to name a desired property on the right side of a but or does. So these:
$x = "Today" but Tue;
$y does True;
expand to:
$x = "Today" but Day::Tue;
$y does Bool::True;
The but and does operators expect a role on their right side. An enum type is not in itself a role type; however, the but and does operators know that when a user supplies an enum type, it implies the generation of an anonymous mixin role that creates an appropriate accessor, read-write if an attribute is being created, and read-only otherwise. It depends on whether you mix in the whole or a specific enum or the whole enumeration:
$x = "Today" but Tue; # $x.Day is read-only
$x = "Today" but Day; # $x.Day is read-write
Mixing in a specific enum object implies only the readonly accessor.
$x = "Today" but Tue;
really means something like:
$x = "Today".clone;
$x does anon role { method Day { Day::Tue } };
The fully qualified form does the same thing, and is useful in case of enum collision:
$x = "Today" but Day::Tue;
Note that the method name is still .Day, however. If you wish to mix in colliding method names, you'll have to mixin your own anonymous role with different method names.
Since an enumeration supplies the type name as a coercion, you can also say:
$x = "Today" but Day(Tue);
$x = "Today" but Day(2);
After any of those
$x.Day
returns Day::Tue (that is, the constant object representing 2), and both the general and specific names function as typenames in normal constraint and coercion uses. Hence,
$x ~~ Day
$x ~~ Tue
$x.Day == Tue
Day($x) == Tue
$x.Tue
all return true, and
$x.Wed
$x.Day == Wed
8 ~~ Day
all return false.
Mixing in the full enumeration type produces a read-write attribute:
$x = "Today" but Day; # read-write .Day
really means something like:
$x = "Today".clone;
$x does anon role { has Day $.Day is rw }
except that nothing happens if there is already a rw attribute of that name.
Note that the attribute is not initialized. If that is desired you can supply a WHENCE closure:
$x = "Today" but Day{ :Day(Tue) }
$x = "Today" but Day{ Tue } # conjecturally, for "simple" roles
To add traits to an enumeration declaration, place them after the declared name but before the list:
enum Size is silly <regular large jumbo>;
To export an enumeration, place the export trait just before the list:
enum Maybe is export <No Yes Dunno>;
To declare that an enumeration implies a particular role, supply a does in the same location
enum Maybe does TristateLogic <No Yes Dunno>;
Two built-in enumerations are:
From t/spec/S02-builtin_data_types/bool.t lines 5–86 (no results): (skip)
| #L<S12/Enumerations/"Two built-in enumerations are">
|
|
|
| # tests True and False are Bool's
|
| isa_ok(Bool::True, Bool);
|
| isa_ok(Bool::False, Bool);
|
|
|
| # tests they keep their Bool'ness when stored
|
| my $a = Bool::True;
|
| isa_ok($a, Bool);
|
|
|
| $a = Bool::False;
|
| isa_ok($a, Bool);
|
|
|
| # tests that Bool.Bool works
|
| isa_ok (Bool::True).Bool, Bool, "Bool.Bool is a Bool";
|
| isa_ok (Bool::False).Bool, Bool, "Bool.Bool is a Bool";
|
| is (Bool::True).Bool, Bool::True, "Bool.Bool works for True";
|
| is (Bool::False).Bool, Bool::False, "Bool.Bool works for False";
|
|
|
| # tests that ?Bool works
|
| isa_ok ?(Bool::True), Bool, "?Bool is a Bool";
|
| isa_ok ?(Bool::False), Bool, "?Bool is a Bool";
|
| is ?(Bool::True), Bool::True, "?Bool works for True";
|
| is ?(Bool::False), Bool::False, "?Bool works for False";
|
|
|
| # tests they work with && and ||
|
| Bool::True && pass('True works');
|
| Bool::False || pass('False works');
|
|
|
| # tests they work with !
|
| !Bool::True || pass('!True works');
|
| !Bool::False && pass('!False works');
|
|
|
| # tests True with ok()
|
| ok(Bool::True, 'True works');
|
|
|
| # tests False with ok() and !
|
| ok(!Bool::False, 'False works');
|
|
|
| # tests Bool stringification - interaction with ~
|
| isa_ok(~Bool::True, Str);
|
| isa_ok(~Bool::False, Str);
|
| ok(~Bool::True, 'stringified True works');
|
| ok(!(~Bool::False), 'stringified False works');
|
| # NOTE. We don't try to freeze ~True into '1'
|
| # and ~False into '' as pugs does now. Maybe we should (?!)
|
|
|
| # numification - interaction with +
|
| ok(+Bool::True ~~ Num);
|
| ok(+Bool::False ~~ Num);
|
| is(+Bool::True, '1', 'True numifies to 1');
|
| is(+Bool::False, '0', 'False numifies to 0');
|
| # stringification
|
| is(~Bool::True, '1', 'True stringifies to 1');
|
| is(~Bool::False, '0', 'False stringifies to 0');
|
|
|
| # Arithmetic operations
|
| my $bool = Bool::False;
|
| is(++$bool, Bool::True, 'Increment of Bool::False produces Bool::True');
|
| is(++$bool, Bool::True, 'Increment of Bool::True still produces Bool::True');
|
| is(--$bool, Bool::False, 'Decrement of Bool::True produces Bool::False');
|
| is(--$bool, Bool::False, 'Decrement of Bool::False produces Bool::False');
|
|
|
| # RT #65514
|
| {
|
| #?rakudo 2 skip 'RT #65514 mix in bool with "but"'
|
| ok (0 but Bool::True), 'Bool::True works with "but"';
|
| is ('RT65514' but Bool::False), 'RT65514', 'Bool::False works with "but"';
|
| }
|
|
|
| #?rakudo skip 'RT 66576: .name method on bool values'
|
| {
|
| is Bool::True.key, 'True', 'Bool::True.key works (is "True")';
|
| is Bool::False.key, 'False', 'Bool::False.key works (is "False")';
|
| }
|
|
|
| #?rakudo todo 'RT 71462: Smartmatch a type yields Int, not a Bool'
|
| isa_ok ('RT71462' ~~ Str), Bool;
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
our enum Bool does Boolean <False True>;
our enum Taint does Tainting <Untainted Tainted>;
Note that Bool and Taint are not role names themselves but imply roles, and the enum values are really subset types of Int, though the constant objects themselves know that they are of type Bool or Taint, and can therefore be used correctly in multimethod dispatch.
You can call the low-level .Bool coercion on any built-in type, because all built-in types do the Boolean role, which requires a .Bool method. Hence, there is a great difference between saying
$x does Boolean; # a no-op, since $x already does Boolean
$x does Bool; # create a $.Bool attribute, also does Boolean
Conditionals evaluate the truth of a boolean expression by testing the return value of .Bool like this:
$obj.Bool != 0
Never compare a value to "True". Just use it in a boolean context. Well, almost never...
If you wish to be explicit about a boolean context, use the high-level so function or ? prefix operator, which are underlying based on the .Bool method. Also, use these high level functions when you wish to autothread junctions, since .Bool forces collapse of a junction's wavefunction. (Similarly, .Str forces stringification of the entire junction, while prefix:<~> does not.)
Like other type names and constant names, enum keynames are parsed as standalone tokens representing scalar values, and don't look for any arguments. Unlike type names but like constant names, enum keynames return defined values. Also unlike types and unlike the enum type as a whole, individual keynames do not respond to .() unless you mix in Callable somehow. (That is, it makes no sense to coerce Wednesday to Tuesday by saying Tue($wed).) Enumerations may not be post-declared.
our enum Maybe <OK FAIL>;
sub OK is redefined {...}
$x = OK; # certainly the enum value
$x = OK() # certainly the function
Since there is an enum OK, the function OK may only be called using parentheses, never in list operator form. (If there is a collision on two enum values that cancels them both, the function still may only be called with parentheses, since the enum key is "poisoned".)
Enumeration types (and perhaps certain other finite, enumerable types such as finite ranges) define a .pick method on the type object of that type. Hence:
my enum CoinFace <Heads Tails>;
CoinFace.pick
returns Heads or Tails with equal probability, and
Month.pick(*)
will return the months in random order. Presumably
StandardPlayingCards.pick(5)
might return a Royal Flush, but a Full House is much more likely. It can never return Five Aces, since the pick is done without replacement. (If it does return Five Aces, it's time to walk away. Or maybe run.)
To pick from the list of keynames or values, derive them via the .enums method described above.
From t/spec/S12-class/open.t lines 5–79 (no results): (skip)
| # L<S12/Open vs Closed Classes>
|
|
|
| class Something {
|
| has $.attribute;
|
| method in_Something { 'a' ~ $.attribute };
|
| }
|
|
|
| my $x = Something.new(attribute => 'b');
|
|
|
| is $x.in_Something, 'ab', 'basic OO sanity';
|
|
|
| # although we use curlies here to be better fudge-able, remeber
|
| # that 'augment' class extensions are *not* lexically scoped
|
| {
|
| augment class Something {
|
| method later_added {
|
| 'later'
|
| }
|
| method uses-other-methods {
|
| 'blubb|' ~ self.in_Something;
|
|
|
| }
|
| }
|
|
|
| my $y = Something.new(attribute => 'c');
|
| is $y.later_added, 'later', 'can call method that was later added';
|
| is $y.uses-other-methods, 'blubb|ac', 'can call new method that calls other methods';
|
|
|
| is $x.later_added, 'later', 'can call method on object that was instantiated earlier';
|
| is $x.uses-other-methods, 'blubb|ab', 'works with other method too';
|
| }
|
|
|
| # now try to extend "core" types
|
|
|
| {
|
| augment class Str {
|
| method mydouble {
|
| self.uc ~ self.lc;
|
| }
|
| }
|
|
|
| is 'aBc'.mydouble, 'ABCabc', 'can extend Str';
|
| }
|
|
|
| {
|
| augment class Int {
|
| method triple { self * 3 }
|
| }
|
| is 3.triple, 9, 'can extend Int';
|
| }
|
|
|
| {
|
| augment class List {
|
| method first-and-last {
|
| self[0] ~ self[self - 1]
|
| }
|
| }
|
|
|
| is <a b c d e f>.first-and-last, 'af', 'can extend class List';
|
| my @a = 1, 3, 7, 0;
|
| is @a.first-and-last, '10', 'can call extended methods from child classes';
|
| }
|
|
|
| {
|
| augment class Array {
|
| method last-and-first {
|
| self[self - 1] ~ self[0]
|
| }
|
| }
|
|
|
| my @a = 1, 3, 7, 0;
|
| is @a.last-and-first, '01', 'can extend class Array';
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
By default, all classes in Perl are non-final, which means you can potentially derive from them. They are also open, which means you can add more methods to them, though you have to be explicit that that is what you're doing:
From t/spec/S12-methods/chaining.t lines 63–79 (no results): (skip)
| # L<S12/"Open vs Closed Classes"/"though you have to be explicit">
|
| #?rakudo skip 'parsing [=>]'
|
| {
|
| # (A => (B => Mu)) => (C => Mu))
|
| # ((A B) C)
|
|
|
| my $cons = [=>] ( [=>] <A B>, Mu ), <C>, Mu;
|
|
|
| ## Hmm. Works with the latest release of Pugs (6.2.12 (r13256))
|
| ## Leaving this in as something that once didn't work (6.2.12 CPAN)
|
|
|
| my $p = $cons.key;
|
| ok( $cons.key.key =:= $p.key, 'chaining through temp variable' );
|
| ok( $cons.key.key =:= $cons.key.key, 'chaining through Any return');
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
augment class Mu {
method wow () { say "Wow, I'm in the Cosmic All." }
}
Otherwise you'll get a class redefinition error. (Also, to completely replace a definition, use "supersede" instead of "augment"...but don't do that, since the compiler may have already committed to optimizations based on the old definition.)
From t/spec/S12-class/augment-supersede.t lines 7–40 (no results): (skip)
| # L<S12/"Open vs Closed Classes"/"Otherwise you'll get a class redefinition error.">
|
|
|
|
|
| #?rakudo emit #
|
| use MONKEY_TYPING;
|
| {
|
| class Foo {
|
| method a {'called Foo.a'}
|
| }
|
| augment class Foo {
|
| method b {'called Foo.b'}
|
| }
|
|
|
| my $o = Foo.new;
|
| is($o.a, 'called Foo.a', 'basic method call works');
|
| is($o.b, 'called Foo.b', 'added method call works');
|
|
|
| ok(!eval('augment class NonExistent { }'), 'augment on non-existent class dies');
|
| }
|
|
|
| {
|
| class Bar {
|
| method c {'called Bar.c'}
|
| }
|
| supersede class Bar {
|
| method d {'called Bar.d'}
|
| }
|
|
|
| my $o = Bar.new;
|
| eval_dies_ok('$o.c', 'overridden method is gone completely');
|
| is($o.d, 'called Bar.d', 'new method is present instead');
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
In order to discourage casual misuse of these declarators, they are not allowed on global classes unless you put a special declaration at the top:
use MONKEY_TYPING;
For optimization purposes, Perl 6 gives the top-level application the right to close and finalize classes by the use of oo, a pragma for selecting global semantics of the underlying object-oriented engine:
From t/spec/S12-class/open_closed.t lines 9–45 (no results): (skip)
| # L<S12/"Open vs Closed Classes"/"a pragma for selecting global semantics of the underlying object-oriented engine">
|
|
|
| use oo :closed :final;
|
|
|
| class Foo {
|
| method a {'called Foo.a'}
|
| }
|
|
|
| eval_dies_ok('augment class Foo {method b {"called Foo.b"}}}', 'adding to closed class dies');
|
|
|
| class Bar is open {
|
| method c {'called Bar.c'}
|
| }
|
| augment class Bar {
|
| method d {'called Bar.d'}
|
| }
|
|
|
| {
|
| my $o = Bar.new;
|
| is($o.c, 'called Bar.c', 'old method is still present');
|
| is($o.d, 'called Bar.d', 'new method is also present');
|
| }
|
|
|
| {
|
| # S12 gives the example of 'use class :open' as well as 'use oo :closed'
|
| # this seems weird to me.
|
| use class :open;
|
| class Baz {method e {'called Baz.e'}}
|
| augment class Baz {
|
| method f {'called Baz.f'}
|
| }
|
|
|
| my $o = Baz.new;
|
| is($o.e, 'called Baz.e', 'old method is still present');
|
| is($o.f, 'called Baz.f', 'new method is present as well');
|
| }
|
|
|
Highlighted:
small|full
use oo :closed :final;
This merely changes the application's default to closed and final, which means that at the end of the main compilation (CHECK time) the optimizer is allowed to look for candidate classes to close or finalize. But anyone (including the main application) can request that any class stay open or nonfinal, and the class closer/finalizer must honor that.
use class :open<Mammal Insect> :nonfinal<Str>
These properties may also be specified on the class definition:
class Mammal is open {...}
class Insect is open {...}
class Str is nonfinal {...}
or by lexically scoped pragma around the class definition:
From t/spec/S12-class/open_closed.t lines 46–58 (no results): (skip)
| # L<S12/"Open vs Closed Classes"/"or by lexically scoped pragma around the class definition">
|
| # and just when you thought I ran out of generic identifiers
|
| use class :open<Qux>;
|
| class Qux {method g {'called Qux.g'}}
|
| {
|
| augment class Qux {
|
| method h {'called Qux.i'}
|
| }
|
| my $o = Qux.new;
|
| is($o.g, 'called Qux.g', 'old is still present');
|
| is($o.h, 'called Qux.h', 'new method is present as well');
|
| }
|
|
|
Highlighted:
small|full
{
use class :open;
class Mammal {...}
class Insect {...}
}
{
use class :nonfinal;
class Str {...}
}
There is no syntax for declaring individual classes closed or final. The application may only request that the optimizer close and finalize unmarked classes.
From t/spec/S12-class/open_closed.t lines 59–64 (no results): (skip)
| # L<S12/"Open vs Closed Classes"/"declaring individual classes closed or final">
|
| # try a few things that come to mind to make sure it's not lurking
|
| eval_dies_ok('class ClosedAlpha is closed {}', '"is closed" is not implemented');
|
| eval_dies_ok('class ClosedBeta is final {}', '"is final" is not implemented');
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
From t/spec/S12-class/interface-consistency.t lines 5–55 (no results): (skip)
| # L<S12/Interface Consistency>
|
|
|
| plan 8;
|
|
|
| class Foo {
|
| method m1($a) {
|
| 1
|
| }
|
| method m2($a, *%foo) {
|
| %foo.keys.elems
|
| }
|
| }
|
|
|
| lives_ok { Foo.new.m1(1, :x<1>, :y<2>) }, 'implicit *%_ means we can pass extra nameds';
|
| ok &Foo::m1.signature.perl ~~ /'*%_'/, '*%_ shows up in .perl of the Signature';
|
| lives_ok { Foo.new.m2(1, :x<1>, :y<2>) }, 'explicit *%_ means we can pass extra nameds';
|
| ok &Foo::m2.signature.perl !~~ /'*%_'/, 'With explicit one, *%_ not in .perl of the Signature';
|
|
|
| class Bar is Foo is hidden {
|
| method m1($a) {
|
| 2
|
| }
|
| }
|
|
|
| dies_ok { Bar.new.m1(1, :x<1>, :y<2>) }, 'is hidden means no implicit *%_';
|
| ok &Bar::m1.signature.perl !~~ /'*%_'/, '*%_ does not show up in .perl of the Signature';
|
|
|
|
|
| class Baz is Bar {
|
| method m1($a) {
|
| nextsame;
|
| }
|
| }
|
|
|
| is Baz.new.m1(42), 1, 'is hidden on Bar means we skip over it in deferal';
|
|
|
|
|
| class Fiz is Foo {
|
| method m1($a) {
|
| 4
|
| }
|
| }
|
| class Faz hides Fiz {
|
| method m1($a) {
|
| nextsame;
|
| }
|
| }
|
|
|
| is Faz.new.m1(42), 1, 'hides Fiz means we skip over Fiz in deferal';
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
By default, all methods and submethods that do not declare an explicit *% parameter will get an implicit *%_ parameter declared for them whether they like it or not. In other words, all methods allow unexpected named arguments, so that nextsame semantics work consistently.
If you mark a class "is hidden", it hides the current class from "nextsame" semantics, and incidentally suppresses the autogeneration of *%_ parameters. Hidden classes may be visited as SUPER::, but not via "next".
A similar effect can be achieved from the derived class by saying hides Base instead of is Base.
Metamethods for objects are named with interrogative pronouns in uppercase:
WHAT the type object of the type, .Str returns MyClass()
From t/spec/S12-methods/what.t lines 13–36 (no results): (skip)
| # L<S12/Introspection/"WHAT">
|
|
|
| # Basic subroutine/method form tests for C<WHAT>.
|
| {
|
| my $a = 3;
|
| ok((WHAT $a) === Int, "subroutine form of WHAT");
|
| ok(($a.WHAT) === Int, "method form of WHAT");
|
| }
|
|
|
| # Now testing basic correct inheritance.
|
| {
|
| my $a = 3;
|
| ok($a.WHAT ~~ Num, "an Int isa Num");
|
| ok($a.WHAT ~~ Mu, "an Int isa Mu");
|
| }
|
|
|
| # And a quick test for Code:
|
| {
|
| my $a = sub ($x) { 100 + $x };
|
| ok($a.WHAT === Sub, "a sub's type is Sub");
|
| ok($a.WHAT ~~ Routine, "a sub isa Routine");
|
| ok($a.WHAT ~~ Code, "a sub isa Code");
|
| }
|
|
|
Highlighted:
small|full
WHICH the object's identity value
WHO the package supporting the object, stringifies to long name
WHERE the memory address of the object
HOW the metaclass object: "Higher Order Workings"
WHEN (reserved for events?)
WHY (reserved for documentation?)
WHENCE autovivification closure
These may be used either as methods or as unary operators:
$obj.WHAT # method form of P5's ref
WHAT $obj # unary form of P5's ref
These are all actually macros, not true operators or methods. If you get a foreign object from another language and need to call its .WHERE method, you can say:
$obj."WHERE"
And if you don't know the method name in advance, you'd be using the variable form anyway:
$obj.$somemeth
which also bypasses the macros.
From t/spec/S12-methods/what.t lines 37–101 (no results): (skip)
| # L<S12/Introspection/"which also bypasses the macros.">
|
|
|
| # RT #60992
|
| {
|
| class Foo {
|
| method WHAT {'Bar'}
|
| }
|
| my $o = Foo.new;
|
| is($o."WHAT"(), 'Bar', '."WHAT" calls the method instead of the macro');
|
| #?rakudo todo '.WHAT not (easily overridable)'
|
| is($o.WHAT, 'Foo', '.WHAT still works as intended');
|
| my $meth = "WHAT";
|
| #?rakudo skip 'indirect method calls'
|
| is($o.$meth, 'Bar', '.$meth calls the method instead of the macro');
|
| }
|
|
|
| # these used to be Rakudo regressions, RT #62006
|
|
|
| #?rakudo skip 'Match object'
|
| {
|
| # proto as a term
|
| lives_ok { Match }, 'proto as a term lives';
|
| lives_ok { +Match }, 'numification of proto lives';
|
| isa_ok ("bac" ~~ /a/).WHAT, Match, '.WHAT on a Match works';
|
| is +("bac" ~~ /a/).WHAT, 0, 'numification of .WHAT of a Match works';
|
| }
|
|
|
| ok &infix:<+>.WHAT ~~ Multi, '.WHAT of built-in infix op is Multi (RT 66928)';
|
|
|
| # RT #69915
|
| {
|
| sub rt69915f( $a, $b ) { return WHAT($a) ~ '~' ~ WHAT($b) }
|
| sub rt69915m( $a, $b ) { return $a.WHAT ~ '~' ~ $b.WHAT }
|
|
|
| is rt69915m( a => 42, 23 ), 'Int()~Int()', 'WHAT method on ints';
|
|
|
| is rt69915f( a => 42, 23 ), 'Int()~Int()', 'WHAT function on ints (1)';
|
| is rt69915f( 23, a => 42 ), 'Int()~Int()', 'WHAT function on ints (2)';
|
|
|
| is rt69915f( :a, 23 ), 'Bool()~Int()', 'WHAT function on bool and int';
|
| is rt69915m( :a, 23 ), 'Bool()~Int()', 'WHAT method on bool and int';
|
|
|
| sub wm($x) { return $x.WHAT }
|
| sub rt69915wm( $a, $b ) { return wm($a) ~ '~' ~ wm($b) }
|
| is rt69915wm( a => 42, 23 ), 'Int()~Int()', 'WHAT method on ints via func';
|
|
|
| sub wf($x) { return WHAT($x) }
|
| sub rt69915wf( $a, $b ) { return wf($a) ~ '~' ~ wf($b) }
|
| is rt69915wf( a => 42, 23 ), 'Int()~Int()', 'WHAT func on ints via func';
|
| }
|
|
|
| is 6.02e23.WHAT, Num, 'decimal using "e" is a Num';
|
| is 1.23456.WHAT, Rat, 'decimal without "e" is Rat';
|
| ok 1.1 == 11/10, 'decimal == the equivalent rational';
|
|
|
| # RT #70237
|
| {
|
| is ~1.WHAT, 'Int()', '1.WHAT sanity';
|
| dies_ok { Int.WHAT = Str }, '.WHAT is readonly';
|
| is ~2.WHAT, 'Int()', 'assignment to Int.WHAT does nothing';
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
For now Perl 6 reserves the right to change how all these macros and the corresponding ^ forms are defined in terms of each other. In particular, the .^ forms will automatically supply the invocant as the first argument to methods of the metaclass, while the other forms require you to pass this explicitly.
Note that WHAT.Str appends () to the name to indicate emptiness. Use .perl to get the bare name from a type object. Use one of .Stringy, prefix:<~>, or infix:<~> to get the Perl5ish semantics of returning the empty string (with a warning) on any type object. (There is no "undef", in Perl 6; type objects provide typed undefs instead.)
In general, use of these uppercased accessors in ordinary code should be a red flag that Something Very Strange is going on. (Hence the allcaps.) Most code should use Perl 6's operators that make use of this information implicitly. For instance, instead of
$obj.WHAT eq 'Dog()'
$x.WHICH === $y.WHICH
$obj.WHAT.bless(%args)
you usually just want:
$obj ~~ Dog
$x === $y
$obj.bless(%args)
Every class has a HOW function/method that lets you get at the class's metaobject, which lets you get at all the metadata properties for the class (or other metaobject protocol) implementing the objects of the class:
MyClass.methods() # call MyClass's .methods method (error?)
MyClass.HOW.methods($obj) # get the method list of MyClass
From t/spec/S12-introspection/methods.t lines 13–137 (no results): (skip)
| # L<S12/Introspection/"get the method list of MyClass">
|
|
|
| class A {
|
| method foo($param --> Any) { }
|
| multi method bar($thingy) { }
|
| multi method bar($thingy, $other_thingy) { }
|
| }
|
| class B is A {
|
| method foo($param) of Num { }
|
| }
|
| class C is A {
|
| }
|
| class D is B is C {
|
| multi method bar($a, $b, $c) { }
|
| method foo($param) returns Int { }
|
| }
|
|
|
| my (@methods, $meth1, $meth2);
|
|
|
| @methods = C.^methods(:local);
|
| is +@methods, 0, 'class C has no local methods (proto)';
|
|
|
| @methods = C.new().^methods(:local);
|
| is +@methods, 0, 'class C has no local methods (instance)';
|
|
|
| @methods = B.^methods(:local);
|
| is +@methods, 1, 'class B has one local methods (proto)';
|
| is @methods[0].name(), 'foo', 'method name can be found';
|
| ok @methods[0].signature.perl ~~ /'$param'/, 'method signature contains $param';
|
| is @methods[0].returns, Num, 'method returns a Num (from .returns)';
|
| is @methods[0].of, Num, 'method returns a Num (from .of)';
|
| ok !@methods[0].multi, 'method is not a multimethod';
|
|
|
| @methods = B.new().^methods(:local);
|
| is +@methods, 1, 'class B has one local methods (instance)';
|
| is @methods[0].name(), 'foo', 'method name can be found';
|
| ok @methods[0].signature.perl ~~ /'$param'/, 'method signature contains $param';
|
| is @methods[0].returns, Num, 'method returns a Num (from .returns)';
|
| is @methods[0].of, Num, 'method returns a Num (from .of)';
|
| ok !@methods[0].multi, 'method is not a multimethod';
|
|
|
| @methods = A.^methods(:local);
|
| is +@methods, 2, 'class A has two local methods (one only + one multi with two variants)';
|
| my ($num_multis, $num_onlys);
|
| for @methods -> $meth {
|
| if $meth.name eq 'foo' {
|
| $num_onlys++;
|
| ok !$meth.multi, 'method foo is not a multimethod';
|
| } elsif $meth.name eq 'bar' {
|
| $num_multis++;
|
| ok $meth.multi, 'method bar is a multimethod';
|
| }
|
| }
|
| is $num_onlys, 1, 'class A has one only method';
|
| is $num_multis, 1, 'class A has one multi methods';
|
|
|
| @methods = D.^methods();
|
| ok +@methods > 5, 'got all methods in hierarchy plus more from Any/Mu';
|
| ok @methods[0].name eq 'foo' && @methods[1].name eq 'bar' ||
|
| @methods[0].name eq 'bar' && @methods[1].name eq 'foo',
|
| 'first two methods from class D itself';
|
| is @methods[2].name, 'foo', 'method from B has correct name';
|
| is @methods[2].of, Num, 'method from B has correct return type';
|
| ok @methods[3].name eq 'foo' && @methods[4].name eq 'bar' ||
|
| @methods[3].name eq 'bar' && @methods[4].name eq 'foo',
|
| 'two methods from class A itself';
|
|
|
| @methods = D.^methods(:tree);
|
| is +@methods, 4, ':tree gives us right number of elements';
|
| ok @methods[0].name eq 'foo' && @methods[1].name eq 'bar' ||
|
| @methods[0].name eq 'bar' && @methods[1].name eq 'foo',
|
| 'first two methods from class D itself';
|
| is @methods[2].WHAT, Array, 'third item is an array';
|
| is +@methods[2], 2, 'nested array for B had right number of elements';
|
| is @methods[3].WHAT, Array, 'forth item is an array';
|
| is +@methods[3], 1, 'nested array for C had right number of elements';
|
| is @methods[2], B.^methods(:tree), 'nested tree for B is correct';
|
| is @methods[3], C.^methods(:tree), 'nested tree for C is correct';
|
|
|
| @methods = List.^methods();
|
| ok +@methods > 0, 'can get methods for List (proto)';
|
| @methods = (1, 2, 3).^methods();
|
| ok +@methods > 0, 'can get methods for List (instance)';
|
|
|
| @methods = Str.^methods();
|
| ok +@methods > 0, 'can get methods for Str (proto)';
|
| @methods = "i can haz test pass?".^methods();
|
| ok +@methods > 0, 'can get methods for Str (instance)';
|
|
|
| ok +List.^methods() > +Any.^methods(), 'List has more methods than Any';
|
| ok +Any.^methods() > +Mu.^methods(), 'Any has more methods than Mu';
|
|
|
| ok +(D.^methods>>.name) > 0, 'can get names of methods in and out of our own classes';
|
| ok D.^methods.perl, 'can get .perl of output of .^methods';
|
|
|
| class PT1 {
|
| method !pm1() { }
|
| method foo() { }
|
| }
|
| class PT2 is PT1 {
|
| method !pm2() { }
|
| method bar() { }
|
| }
|
|
|
| @methods = PT2.^methods();
|
| is @methods[0].name, 'bar', 'methods call found public method in subclass';
|
| is @methods[1].name, 'foo', 'methods call found public method in superclass (so no privates)';
|
| ok @methods[2].name ne '!pm1', 'methods call did not find private method in superclass';
|
|
|
| @methods = PT2.^methods(:private);
|
| ok @methods[0].name eq '!pm2' || @methods[1].name eq '!pm2',
|
| 'methods call with :private found private method in subclass';
|
| ok @methods[2].name eq '!pm1' || @methods[3].name eq '!pm1',
|
| 'methods call with :private found private method in superclass';
|
|
|
| @methods = PT2.^methods(:local);
|
| is +@methods, 1, 'methods call without :private omits private methods (with :local)';
|
| is @methods[0].name, 'bar', 'methods call found public method in subclass (with :local)';
|
|
|
| @methods = PT2.^methods(:local, :private);
|
| is +@methods, 2, 'methods call with :private includes private methods (with :local)';
|
| ok @methods[0].name eq '!pm2' || @methods[1].name eq '!pm2',
|
| 'methods call with :private found private method in subclass (with :local)';
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
The ^ metasyntax is equivalent to .HOW:
MyClass.HOW.methods($obj) # get the method list of MyClass
^MyClass.methods($obj) # get the method list of MyClass
MyClass.^methods() # get the method list of MyClass
Each object of the class also has a .HOW or .^ method:
$obj.HOW.methods($obj);
$obj.^methods();
(If you are using prototype-based OO rather than class-based, you must use the object form, since every such object functions as its own class.)
Class traits may include:
From t/spec/S12-introspection/meta-class.t lines 33–42 (no results): (skip)
| # L<S12/Introspection/Class traits may include:>
|
|
|
| #?rakudo skip '.name'
|
| is Foo.^name(), 'Foo', '... the name() property is Foo';
|
| #?rakudo skip '.version, version number parsing'
|
| is Foo.^version(), v0.0.1, '... the version() property is 0.0.1';
|
| #?rakudo skip '.layout'
|
| is Foo.^layout, P6opaque, '^.layout';
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
identifier { :name<Dog> :auth<http://www.some.com/~jrandom> :ver<1.2.1> }
name Dog
authority http://www.some.com/~jrandom
version v1.2.1
author Joe Random
description This class implements camera obscura.
subject optics, boxes
language ja_JP
licensed Artistic|GPL
parents list of parent classes
roles list of roles
From t/spec/S12-introspection/roles.t lines 13–43 (no results): (skip)
| # L<S12/Introspection/"list of roles">
|
|
|
| role R1 { }
|
| role R2 { }
|
| role R3 { }
|
| class C1 does R1 does R2 { }
|
| class C2 is C1 does R3 { }
|
|
|
| my @roles = C2.^roles(:local);
|
| is +@roles, 1, ':local returned list with correct number of roles';
|
| is @roles[0], R3, 'role in list was correct';
|
|
|
| @roles = C1.^roles(:local);
|
| is +@roles, 2, ':local returned list with correct number of roles';
|
| ok (@roles[0] ~~ R1 && @roles[1] ~~ R2 || @roles[0] ~~ R2 && @roles[1] ~~ R1),
|
| 'roles in list were correct';
|
|
|
| @roles = C2.^roles();
|
| is +@roles, 3, 'with no args returned list with correct number of roles';
|
| is @roles[0], R3, 'first role in list was correct';
|
| ok (@roles[1] ~~ R1 && @roles[2] ~~ R2 || @roles[1] ~~ R2 && @roles[2] ~~ R1),
|
| 'second and third roles in list were correct';
|
|
|
| @roles = C2.^roles(:tree);
|
| is +@roles, 2, ':tree returned list with correct number of elements';
|
| is @roles[0], R3, 'first element in the list is the role done in base class';
|
| ok @roles[1] ~~ Array, 'second element in list is an array';
|
| ok @roles[1][0] ~~ R1 && @roles[1][1] ~~ R2 || @roles[1][0] ~~ R2 && @roles[1][1] ~~ R1,
|
| 'nested array contains roles of parent class';
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
disambig how to deal with ambiguous method names from roles
layout P6opaque, P6hash, P5hash, P5array, PyDict, Cstruct, etc.
These are for the standard Perl 6 Meta-Object Protocol, but other MOPs may define other traits. The identifier should probably be accessed through the .WHO object in any case, which may have its own object methods depending on how type namespaces evolve over time. Which of these items are actually part of the .HOW object and which are delegated back to the package and prototype objects is up to the MOP. (Note also that anonymous classes may have anonymous packages and prototype objects, in which case stringification is not likely to produce something of interest to non-gurus.)
The .^parents method by default returns a flattened list of all parents sorted in MRO (dispatch) order. Other options are:
From t/spec/S12-introspection/parents.t lines 13–108 (no results): (skip)
| # L<S12/Introspection/"The .^parents method">
|
| class A { }
|
| class B is A { }
|
| class C is A { }
|
| class D is B is C { }
|
| my @parents;
|
|
|
| @parents = A.^parents();
|
| is +@parents, 2, 'right number of parents in list of all, from proto-object';
|
| is @parents[0].WHAT, 'Any()', 'first parent is Any';
|
| is ~@parents[1].WHAT, 'Mu()', 'second parent is Mu';
|
|
|
| @parents = A.new.^parents();
|
| is +@parents, 2, 'right number of parents in list of all, from instance';
|
| is @parents[0].WHAT, 'Any()', 'first parent is Any';
|
| is ~@parents[1].WHAT, 'Mu()', 'second parent is Mu';
|
|
|
| @parents = D.^parents();
|
| is +@parents, 5, 'right number of parents in list of all, from proto-object, multiple inheritance';
|
| is @parents[0].WHAT, 'B()', 'first parent is B';
|
| is @parents[1].WHAT, 'C()', 'second parent is C';
|
| is @parents[2].WHAT, 'A()', 'third parent is A';
|
| is @parents[3].WHAT, 'Any()', 'forth parent is Any';
|
| is ~@parents[4].WHAT, 'Mu()', 'fifth parent is Mu';
|
|
|
| @parents = D.new.^parents();
|
| is +@parents, 5, 'right number of parents in list of all, from instance, multiple inheritance';
|
| is @parents[0].WHAT, 'B()', 'first parent is B';
|
| is @parents[1].WHAT, 'C()', 'second parent is C';
|
| is @parents[2].WHAT, 'A()', 'third parent is A';
|
| is @parents[3].WHAT, 'Any()', 'forth parent is Any';
|
| is ~@parents[4].WHAT, 'Mu()', 'fifth parent is Mu';
|
|
|
| @parents = B.^parents(:local);
|
| is +@parents, 1, 'right number of parents in list, from proto-object, :local';
|
| is @parents[0].WHAT, 'A()', 'parent is A';
|
|
|
| @parents = B.new.^parents(:local);
|
| is +@parents, 1, 'right number of parents in list, from instance, :local';
|
| is @parents[0].WHAT, 'A()', 'parent is A';
|
|
|
| @parents = D.^parents(:local);
|
| is +@parents, 2, 'right number of parents in list, from proto-object, :local, multiple inheritance';
|
| is @parents[0].WHAT, 'B()', 'first parent is B';
|
| is @parents[1].WHAT, 'C()', 'second parent is C';
|
|
|
| @parents = D.new.^parents(:local);
|
| is +@parents, 2, 'right number of parents in list, from instance, :local, multiple inheritance';
|
| is @parents[0].WHAT, 'B()', 'first parent is B';
|
| is @parents[1].WHAT, 'C()', 'second parent is C';
|
|
|
| @parents = D.^parents(:tree);
|
| is +@parents, 2, 'with :tree, D has two immediate parents (on proto)';
|
| ok @parents[0] ~~ Array, ':tree gives back nested arrays for each parent (on proto)';
|
| ok @parents[1] ~~ Array, ':tree gives back nested arrays for each parent (on proto)';
|
| is @parents, [[B, [A, [Any, [Mu]]]], [C, [A, [Any, [Mu]]]]],
|
| ':tree gives back the expected data structure (on proto)';
|
|
|
| @parents = D.new.^parents(:tree);
|
| is +@parents, 2, 'with :tree, D has two immediate parents (on instance)';
|
| ok @parents[0] ~~ Array, ':tree gives back nested arrays for each parent (on instance)';
|
| ok @parents[1] ~~ Array, ':tree gives back nested arrays for each parent (on instance)';
|
| is @parents, [[B, [A, [Any, [Mu]]]], [C, [A, [Any, [Mu]]]]],
|
| ':tree gives back the expected data structure (on instance)';
|
|
|
| @parents = List.^parents();
|
| is +@parents, 4, 'right number of parents for List built-in, from proto-object';
|
| is @parents[0].WHAT, 'Iterator()', 'first parent is Iterator';
|
| is @parents[1].WHAT, 'Iterable()', 'second parent is Iterable';
|
| is @parents[2].WHAT, 'Any()', 'third parent is Any';
|
| is ~@parents[3].WHAT, 'Mu()', 'forth parent is Mu';
|
|
|
| @parents = (1,2,3).Seq.^parents();
|
| is +@parents, 3, 'right number of parents for Seq built-in, from instance';
|
| is @parents[0].WHAT, 'Iterable()', 'first parent is Any';
|
| is @parents[1].WHAT, 'Any()', 'second parent is Any';
|
| is ~@parents[2].WHAT, 'Mu()', 'third parent is Mu';
|
|
|
| @parents = Str.^parents();
|
| is +@parents, 2, 'right number of parents for Str built-in, from proto-object';
|
| is @parents[0].WHAT, 'Any()', 'first parent is Any';
|
| is ~@parents[1].WHAT, 'Mu()', 'second parent is Mu';
|
|
|
| @parents = "omg introspection!".^parents();
|
| is +@parents, 2, 'right number of parents for Str built-in, from instance';
|
| is @parents[0].WHAT, 'Any()', 'first parent is Any';
|
| is ~@parents[1].WHAT, 'Mu()', 'second parent is Mu';
|
|
|
| @parents = Mu.^parents();
|
| is +@parents, 0, 'Mu has no parents (no params)';
|
| @parents = Mu.^parents(:local);
|
| is +@parents, 0, 'Mu has no parents (:local)';
|
| @parents = Mu.^parents(:tree);
|
| is +@parents, 0, 'Mu has no parents (:tree)';
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
:local just returns the immediate parents
:tree the inheritance hierarchy as nested arrays
The .^methods method returns method-descriptors containing:
name the name of the method
signature the parameters of the method
as the coercion type of the method
multi whether duplicate names are allowed
do the method body
The .^methods method has a selector parameter that lets you specify whether you want to see a flattened or hierarchical view, whether you're interested in private methods, and so forth.
:local only methods defined in the current class
:tree methods by class structure (inheritance hierarchy)
:private include private methods
Note that, since introspection is primarily for use by the outside world (the class already knows its own structure, after all), a set of multi methods are presented to be a single Routine object. You need to use .candidates on that to break it down further.
The .^attributes method returns a list of attribute descriptors that have traits like these:
From t/spec/S12-introspection/attributes.t lines 13–63 (no results): (skip)
| # L<S12/Introspection/"The .^attributes method">
|
|
|
| class A {
|
| has Str $.a = "dnes je horuci a potrebujem pivo";
|
| }
|
| class B is A {
|
| has Int $!b = 42;
|
| }
|
| class C is B {
|
| has $.c is rw
|
| }
|
|
|
| my @attrs = C.^attributes();
|
| is +@attrs, 3, 'attribute introspection gave correct number of elements';
|
|
|
| is @attrs[0].name, '$!c', 'first attribute had correct name';
|
| is @attrs[0].type, Mu, 'first attribute had correct type';
|
| is @attrs[0].has-accessor, True, 'first attribute has an accessor';
|
| ok !@attrs[0].build, 'first attribute has no build value';
|
| ok @attrs[0].rw, 'first attribute is rw';
|
| ok !@attrs[0].readonly, 'first attribute is not readonly';
|
|
|
| is @attrs[1].name, '$!b', 'second attribute had correct name';
|
| is @attrs[1].type, Int, 'second attribute had correct type';
|
| is @attrs[1].has-accessor, False, 'second attribute has no accessor';
|
| ok @attrs[1].build ~~ Code, 'second attribute has build block';
|
| is @attrs[1].build().(C, $_), 42,
|
| 'second attribute build block gives expected value';
|
|
|
| is @attrs[2].name, '$!a', 'third attribute had correct name';
|
| is @attrs[2].type, Str, 'third attribute had correct type';
|
| is @attrs[2].has-accessor, True, 'third attribute has an accessor';
|
| ok @attrs[2].build ~~ Code, 'third attribute has build block';
|
| is @attrs[2].build().(C, $_), "dnes je horuci a potrebujem pivo",
|
| 'third attribute build block gives expected value';
|
| ok !@attrs[2].rw, 'third attribute is not rw';
|
| ok @attrs[2].readonly, 'third attribute is readonly';
|
|
|
| @attrs = C.^attributes(:local);
|
| is +@attrs, 1, 'attribute introspection with :local gave just attribute in base class';
|
| is @attrs[0].name, '$!c', 'get correct attribute with introspection';
|
|
|
|
|
| @attrs = C.^attributes(:tree);
|
| is +@attrs, 2, 'attribute introspection with :tree gives right number of elements';
|
| is @attrs[0].name, '$!c', 'first element is attribute desriptor';
|
| ok @attrs[1] ~~ Array, 'second element is array';
|
| is @attrs[1][0].name, '$!b', 'can look into second element array to find next attribute';
|
| is @attrs[1][1][0].name, '$!a', 'can look deeper to find attribute beyond that';
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
name
type
scope
rw
private
has-accessor
build
readonly
It also takes the parameters:
:local only methods defined in the current class
:tree attributes by class structure (inheritance hierarchy)
Strictly speaking, metamethods like .isa(), .does(), and .can() should be called through the meta object:
From t/spec/S12-introspection/meta-class.t lines 19–32 (no results): (skip)
| # L<S12/Introspection/should be called through the meta object>
|
|
|
| #?pugs emit skip_rest('meta class NYI');
|
| #?pugs emit exit;
|
|
|
| ok(Foo.HOW.can(Foo, 'bar'), '... Foo can bar');
|
| #?rakudo skip 'precedence of HOW'
|
| ok(HOW(Foo).can(Foo, 'bar'), '... Foo can bar (anthoer way)');
|
| #?rakudo skip 'precedence of prefix:<^>'
|
| ok(^Foo.can(Foo, 'bar'), '... Foo can bar (another way)');
|
| ok(Foo.^can('bar'), '... Foo can bar (as class method)');
|
| ok(Foo.HOW.isa(Foo, Foo), '... Foo is-a Foo (of course)');
|
| ok(Foo.^isa(Foo), '... Foo is-a Foo (of course) (as class method)');
|
|
|
Highlighted:
small|full
$obj.HOW.can($obj, "bark")
$obj.HOW.does($obj, Dog)
$obj.HOW.isa($obj, Mammal)
or
$obj.^can("bark")
$obj.^does(Dog)
$obj.^isa(Mammal)
But Any gives you shortcuts to those:
$obj.can("bark")
$obj.does(Dog)
$obj.isa(Mammal)
These, may, of course, be overridden in a subclass, so don't use the short form unless you wish to allow for overrides. In general, Any will delegate only those metamethods that read well when reasoning about an individual object. Infrastructural methods like .^methods and .^attributes are not delegated, so $obj.methods fails.
The smartmatch:
$obj ~~ Dog
actually calls:
$obj.HOW.does($obj, Dog)
which is true if $obj either "does" or "isa" Dog (or "isa" something that "does" Dog). If Dog is a subset, any additional where constraints must also evaluate true.
Unlike in Perl 5 where .can returns a single Code object, Perl 6's version of .^can returns a "WALK" iterator for a set of routines that match the name, including all autoloaded and wildcarded possibilities. In particular, .^can interrogates any class package's CANDO method for names that are to be considered autoloadable methods in the class, even if they haven't been declared yet. Role composition sometimes relies on this ability to determine whether a superclass supplies a method of a particular name if it's required and hasn't been supplied by the class or one of its roles.
From t/spec/S12-introspection/can.t lines 13–92 (no results): (skip)
| # L<S12/"Introspection"/Unlike in Perl 5 where .can returns a single Code object>
|
|
|
| lives_ok { Str.can("split") }, "method can on built-in Str works";
|
| ok "foo".can("split"), "methd can on built-in Str gives correct result if method found";
|
| ok !"foo".can("hazcheezburger"), "methd can on built-in Str gives correct result if method not found";
|
| ok "bar".^can("split"), "calling ^can also works";
|
| ok "x".HOW.can("x", "split"), "and also through the HOW";
|
| ok Str.can("split"), "can call on the proto-object too";
|
| ok !Str.can("hazcheezburger"), "can call on the proto-object too";
|
|
|
| class Dog {
|
| method bark {
|
| "bow";
|
| }
|
| }
|
|
|
| my $dog = Dog.new;
|
| lives_ok { $dog.can("bark") }, "method can on custom class works";
|
| ok $dog.can("bark"), "method can on custom class gives correct result if method found (on instance)";
|
| ok !$dog.can("w00f"), "method can on custom class gives correct result if method not found (on instance)";
|
| ok Dog.can("bark"), "method can on custom class gives correct result if method found (on proto)";
|
| ok !Dog.can("w00f"), "method can on custom class gives correct result if method not found (on proto)";
|
|
|
| my $meth = $dog.can("bark");
|
| is $meth($dog), "bow", "the result for can is an invokable, giving us the sub (on instance)";
|
| $meth = Dog.can("bark");
|
| is $meth(Dog), "bow", "the result for can is an invokable, giving us the sub (on proto)";
|
|
|
| {
|
| my $iters = 0;
|
| my $found = "";
|
| for $dog.can("bark") -> $meth {
|
| $found ~= $meth($dog);
|
| $iters++;
|
| }
|
| is $iters, 1, "had right number of methods found (on instance)";
|
| is $found, "bow", "got right method called (on instance)";
|
| }
|
|
|
| {
|
| my $iters = 0;
|
| my $found = "";
|
| for Dog.can("bark") -> $meth {
|
| $found ~= $meth($dog);
|
| $iters++;
|
| }
|
| is $iters, 1, "had right number of methods found (on proto)";
|
| is $found, "bow", "got right method called (on proto)";
|
| }
|
|
|
| class Puppy is Dog {
|
| method bark {
|
| "yap";
|
| }
|
| }
|
| my $pup = Puppy.new();
|
|
|
| {
|
| my $iters = 0;
|
| my $found = "";
|
| for $pup.can("bark") -> $meth {
|
| $found ~= $meth($pup);
|
| $iters++;
|
| }
|
| is $iters, 2, "subclass had right number of methods found (on instance)";
|
| is $found, "yapbow", "subclass got right methods called (on instance)";
|
| }
|
|
|
| {
|
| my $iters = 0;
|
| my $found = "";
|
| for Puppy.can("bark") -> $meth {
|
| $found ~= $meth($pup);
|
| $iters++;
|
| }
|
| is $iters, 2, "subclass had right number of methods found (on proto)";
|
| is $found, "yapbow", "subclass got right methods called (on proto)";
|
| }
|
|
|
| # vim: ft=perl6
|
Highlighted:
small|full
The WHENCE property of an object is its autovivifying closure. Any undefined prototype object may carry such a closure that can lazily create an object of the appropriate type. When the closure is eventually evaluated it is expected to return an argument list corresponding to the arguments to a .bless call. For instance, a CANDO routine, instead of creating a Dog object directly, could instead return something like:
Dog but WHENCE({ :name<Fido> })
which runs the closure if the object ever needs to be autovivified. The closure can capture whatever initializers were available in the original lexical scope.
The short form of the above is simply:
Dog{ :name<Fido> }
This form is also lazily evaluated:
my $dog = Dog{ :name<Fido> };
defined $dog or say "doesn't exist"; # Fido doesn't exist
$dog.wag() # Fido wags his tail
When the typename happens to be a role, autovivifying it involves attempting to create a punned class of the same name as the role. Whether this succeeds or not depends on whether the role is sufficiently complete to serve as a class on its own. Regardless of whether such an attempt would succeed, it is always perfectly fine to define a lazy type object for a role just as long as it's only ever used as an argument to bless, since bless will only be using its closure to construct the role's BUILD arguments in the context of the complete new class. (Of course, an inconsistent or incomplete class composition may subsequently fail, and in fact the incomplete role autovivification mentioned above is likely to be implemented by failing at the point of class composition.)
Note that when used as an argument to a method like bless, the type object is sufficiently lazy that autovivifying is done only by the appropriate BUILD routine. It does not waste energy creating a Dog object when that object's attributes would later have to be copied into the actual object. (On top of which, such an implementation would make it impossible to use type objects to initialize incomplete roles.)
The object autovivification syntax works only for literal named types, so any indirection must be written more explicitly:
::($dogproto){ :name<Fido> }
$dogproto but WHENCE({ :name<Fido> })
$dogproto.WHAT{ :name<Fido> }
Note that in contrast to this syntax, a lookup of a symbol in the Dog package requires a final :: before the subscript:
Dog::{$varname}
[ Top ]
[ Index of Synopses ]