Synopsis
package MyApp { use Zydeco; class JSON::Encoder { multi method stringify (Undef $value) { 'null'; } multi method stringify (ScalarRef[Bool] $value) { $$value ? 'true' : 'false'; } multi method stringify (Num $value) { $value; } multi method stringify :alias(quote_str) (Str $value) { sprintf(q<"%s">, quotemeta $value); } multi method stringify (ArrayRef $arr) { sprintf( q<[%s]>, join(q<,>, map($self->stringify($_), @$arr)) ); } multi method stringify (HashRef $hash) { sprintf( q<{%s}>, join( q<,>, map sprintf( q<%s:%s>, $self->quote_str($_), $self->stringify($hash->{$_}), ), sort keys %$hash ), ); } } }
It is pretty common within a method to accept two different kinds of inputs and process them in different ways depending on their type.
method foobar ( Str|ArrayRef $x ) { if (is_Str $x) { ...; } else { ...; } }
The multi method
keyword can make this a little more concise and readable.
multi method foobar ( Str $x ) { ...; } multi method foobar ( ArrayRef $x ) { ...; }
Multimethods can have different numbers of arguments, named arguments, etc.
multi method print_name () { print $self->name, "\n"; } multi method print_name ( FileHandle $fh ) { print {$fh} $self->name, "\n"; } multi method print_name ( Str $format, $fh= \*STDOUT ) { printf {$arg->fh} $format, $self->name; } $person->print_name; $person->print_name( \*STDERR ); $person->print_name( format => "NAME: %\n" );
Multimethods and Inheritance
It is possible for child classes to add additional "candidates" to a multimethod.
package MyApp { use Zydeco; class Foo { multi method foobar ( Str $x ) { ...; } } class Bar extends Foo { multi method foobar ( ArrayRef $x ) { ...; } } }
The method "foobar" on objects of the "Foo" class will only accept strings. Calling it on a "Bar" object will allow arrayrefs or strings.
Multimethods and Roles
Multimethods can be defined in roles and will compose together into the classes that consume them.
package MyApp { use Zydeco; role Foo { multi method foobar ( Str $x ) { ...; } } role Bar { multi method foobar ( HashRef $x ) { ...; } multi method foobar ( ArrayRef $x ) { ...; } } class Foo::Bar with Foo, Bar { multi method foobar ( ArrayRef $x ) { ...; } } }
The "foobar" method in "Foo::Bar" will accept strings, arrayrefs, and hashrefs. Its own implementation for arrayrefs will override the one found in the "Bar" role.
Multimethod Candidate Selection
Sometimes multiple candidates will match the given parameters.
multi method foobar ( Num $x ) { ...; } multi method foobar ( Int $x ) { ...; } $object->foobar(123);
The number 123 is both an integer and a number, so which one "wins"? Int
wins because it's a more specific type constraint.
In general:
- More specific type constraints beat more general type constraints.
- Candidates in subclasses beat candidates inherited from parent classes.
- Candidates defined directly in a class beat those imported from roles.
- Candidates declared earlier beat candidates declared later.
For all the gory details, see Sub::MultiMethod.
Multi Factories
Yes, multi
and factory
can be used together.
package MyApp { use Zydeco; class Person { has name, age; multi factory new_person ( Str $name ) { $class->new( name => $name ); } multi factory new_person ( Str $name, Num $age ) { $class->new( name => $name, age => $age ); } multi factory new_person ( HashRef $args ) { $class->new( %$args ); } } } my $alice = MyApp->new_person("Alice"); my $bob = MyApp->new_person("Bob", 49); my $carol = MyApp->new_person({ name => "Carol" });
Internally this works by creating a multi method called "__multi_factory_new_person" and creating a factory method which calls that.
The via
syntax isn't supported with multi factories.
Abbreviated Syntax
The usual abbreviated syntax will work for multimethods.
multi method stringify (Undef $value) = 'null';
Keywords
In this chapter, we looked at the following keyword:
multi method
We have looked at four ways to define methods: method
, factory
, multi method
, and multi factory
. Now let's look at how to modify existing methods.
- Zydeco::Manual::06_MethodModifiers - Method modifiers to easily wrap or override inherited methods