Make your Moose safe and clean:
Method Signatures + Multi Methods + Types

Munich Perl Mongers Technical Meeting 11/2013

David Kaumanns

The task

$human->drink( 'coffee', size => 'xxl', with_milk => 1 );
$human->drink( 'chocolate', size => 'xxl' );

Classic Moose “method”

sub drink {
    my $self = shift;
    my $beverage = shift;

    ...
}

… with named parameters

sub drink {
    my $self = shift;
    my $beverage = shift;
    my $o = { @_ };

    ...
}

… with case handling

sub drink {
    my $self = shift;
    my $beverage = shift;
    my $o = { @_ };

    if ( $beverage eq 'coffee' ) {





        ...
    }
    elsif ( $beverage eq 'chocolate' ) {


        ...
    }
    else {
        ...
    }

    ...
}

… with default values

sub drink {
    my $self = shift;
    my $beverage = shift;
    my $o = { @_ };

    if ( $beverage eq 'coffee' ) {



        my $with_milk = ( $o->{with_milk} or 0 );

        ...
    }
    elsif ( $beverage eq 'chocolate' ) {


        ...
    }
    else {
        ...
    }

    ...
}

… with requirements

sub drink {
    my $self = shift;
    my $beverage = shift;
    my $o = { @_ };

    if ( $beverage eq 'coffee' ) {
        die "Need size" unless exists $o->{size};


        my $with_milk = ( $o->{with_milk} or 0 );

        ...
    }
    elsif ( $beverage eq 'chocolate' ) {
        die "Need size" unless exists $o->{size};

        ...
    }
    else {
        ...
    }

    ...
}

… with verification

sub drink {
    my $self = shift;
    my $beverage = shift;
    my $o = { @_ };

    if ( $beverage eq 'coffee' ) {
        die "Need size" unless exists $o->{size};
        die "Invalid size" unless $o->{size} =~ /^(?:s|m|l|xl|xxl)$/;

        my $with_milk = ( $o->{with_milk} or 0 );
        die "Invalid milk choice" unless $with_milk =~ /^[01]$/;
        ...
    }
    elsif ( $beverage eq 'chocolate' ) {
        die "Need size" unless exists $o->{size};
        die "Invalid size" unless $o->{size} =~ /^(?:s|m|l|xl|xxl)$/;
        ...
    }
    else {
        ...
    }

    ...
}

We are still missing other parameters

… and the code is already

Positional and named parameters

method drink ( Str $beverage, Str :$size, Bool :$with_milk ) {
    ...
}

Required and optional parameters

method drink ( Str $beverage!, Str :$size!, Bool :$with_milk? ) {
    ...
}

Default values

method drink ( Str $beverage!, Str :$size!, Bool :$with_milk? = 0 ) {
    ...
}

Non-native verification (constraints)

method drink ( Str $beverage! where sub { /^(?:coffee|chocolate)$/ }, Str :$size! where sub { /^(?:s|m|l|xl|xxl)$/ }, Bool :$with_milk? = 0 }) {
    ...
}

Non-native verification (constraints)

method drink (
    Str     $beverage!          where sub { /^(?:coffee|chocolate)$/ },
    Str     :$size!             where sub { /^(?:s|m|l|xl|xxl)$/ },
    Bool    :$with_milk?    = 0
) {
    ...
}

Case handling

Use MooseX::MultiMethods!

multi method drink (
    Str     $beverage!          where sub { $_ eq 'coffee' },
    Str     :$size!,            where sub { /^(?:s|m|l|xl|xxl)$/ }
    Bool    :$with_milk?    = 0
) { ... }

multi method drink (
    Str     $beverage!          where sub { $_ eq 'chocolate' },
    Str     :$size!,            where sub { /^(?:s|m|l|xl|xxl)$/ }
) { ... }

multi method drink (
    Str     $beverage!
) { ... }

Or better yet: Verification and case handling via custom types

subtype Beverage,
    as      Str,
    where   { /^(?:
        coffee
        |chocolate
        |tea
    )$/x },
    message { "$_ is no beverage." };

Or better yet: Verification and case handling via custom types

subtype Beverage,
    as      Str,
    where   { /^(?:
        coffee
        |chocolate
        |tea
    )$/x },
    message { "$_ is no beverage." };

subtype Coffee,
    as      Beverage,
    where   { $_ eq 'coffee' };

subtype Chocolate,
    as      Beverage,
    where   { $_ eq 'chocolate' };

Or better yet: Verification and case handling via custom types

subtype Beverage,
    as      Str,
    where   { /^(?:
        coffee
        |chocolate
        |tea
    )$/x },
    message { "$_ is no beverage." };

subtype Coffee,
    as      Beverage,
    where   { $_ eq 'coffee' };

subtype Chocolate,
    as      Beverage,
    where   { $_ eq 'chocolate' };

subtype Size,
    as      Str,
    where   { /^(?:s|m|l|xl|xxl)$/ },
    message { "$_ is no size." };

Our new multi methods

multi method drink (
    Coffee      $beverage!,
    Size        :$size!,
    Bool        :$with_milk?    = 0
) { ... }

multi method drink (
    Chocolate   $beverage!,
    Size        :$size!
) { ... }

multi method drink (
    Beverage   $beverage!
) { ... }

Coercion in action

subtype Coffee,
    as      Beverage,
    where   { $_ eq 'coffee' };

Coercion in action

subtype Coffee,
    as      Beverage,
    where   { $_ eq 'coffee' };

coerce Coffee,
    from CoffeePowder,
        via { $_->brew };

Coercion in action

subtype Coffee,
    as      Beverage,
    where   { $_ eq 'coffee' };

coerce Coffee,
    from CoffeePowder,
        via { $_->brew };
multi method drink ( Coffee $beverage! does coerce, ... ) { ... }

Coercion in action

subtype Coffee,
    as      Beverage,
    where   { $_ eq 'coffee' };

coerce Coffee,
    from CoffeePowder,
        via { $_->brew };
multi method drink ( Coffee $beverage! does coerce, ... ) { ... }

Summary

Make this…

sub drink {
    my $self = shift;
    my $beverage = shift;
    my $o = { @_ };

    if ( $beverage eq 'coffee' ) {
        die "Need size" unless exists $o->{size};
        die "Invalid size" unless $o->{size} =~ /^(?:s|m|l|xl|xxl)$/;

        my $with_milk = ( $o->{with_milk} or 0 );
        die "Invalid milk choice" unless $with_milk =~ /^[01]$/;
        ...
    }
    elsif ( $beverage eq 'chocolate' ) {
        die "Need size" unless exists $o->{size};
        die "Invalid size" unless $o->{size} =~ /^(?:s|m|l|xl|xxl)$/;
        ...
    }
    else {
        ...
    }

    ...
}

into this…

multi method drink (
    Coffee      $beverage!      does coerce,
    Size        :$size!,
    Bool        :$with_milk?    = 0
) { ... }

multi method drink (
    Chocolate   $beverage!,
    Size        :$size!
) { ... }

multi method drink (
    Beverage   $beverage!
) { ... }

… with your new type library

subtype Beverage,
    as      Str,
    where   { /^(?:
        coffee
        |chocolate
        |tea
    )$/x },
    message { "$_ is no beverage." };

subtype Coffee,
    as      Beverage,
    where   { $_ eq 'coffee' };

coerce Coffee,
    from CoffeePowder,
        via { $_->brew };

subtype Chocolate,
    as      Beverage,
    where   { $_ eq 'chocolate' };

subtype Size,
    as      Str,
    where   { /^(?:s|m|l|xl|xxl)$/ },
    message { "$_ is no size." };

Profit

Spread the message.

https://metacpan.org/pod/MooseX::Method::Signatures

https://metacpan.org/pod/MooseX::MultiMethods

https://metacpan.org/pod/Moose::Util::TypeConstraints