Zydeco » Manual » 05_Multimethods

multi methods

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
Next Steps

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.