Day 21 – Signatures

by

In today’s post, we’ll go through the basics of Perl 6′s subroutine signatures, which allow us to declare our parameters instead of coding them as we do in Perl 5.

In Perl 5, arguments to a sub are accessible via the @_ array, so if you want to access an argument to a sub, you can either access the array directly (which is typically only done where speed is a concern)…

use v5;
sub greet {
    print "Hello, " . @_[0] . "\n";
}

Or, more commonly, pull off any arguments as lexicals:

use v5;
sub greet {
    my $name = shift @_;
    print "Hello, $name\n";
}

Positionals

Perl 6 lets you declare required positional arguments for your subs as part of the signature:

use v6;
sub greet($name) {
    print "Hello, $name\n";
}

Inside the sub, $name is available as a readonly alias to the passed in variable.

By default, parameters are required. If you try to call this function with no parameters as greet, you’ll get a compile time error:

===SORRY!===
CHECK FAILED:
Calling 'greet' requires arguments (line 1)
Expected: :($name)

You can make the parameter optional by adding a ? to the signature. Then you’ll have to examine the parameter to see if a defined value was passed in. Or, you can declare a default value to be used if none is passed in:

use v6;
sub guess($who, $what?) {
    # manually check to see if $what is defined
}

sub dance($who, $dance = "Salsa") {
    ...
}
dance("Rachael")
dance("Rachael", "Watusi")

Another way to handle optional arguments is to use multis. Multis are subs that can have multiple implementations that differ by their signature. When invoking a multi, the argument list used in the invocation is used to determine which version of the multi to dispatch to.

use v6;
multi sub dance($who, $dance) {
    say "$who is doing the $dance";
}
multi sub dance($who) {
    dance($who, "Salsa");
}

Types

The parameters in the previous section allow any type of value to be passed in. You can declare that only certain types are valid:

sub greet(Str $name) {
    say "hello $name";
}

Now hello("joe") will work as it did before. But hello(3) will generate a compile time error:

===SORRY!===
CHECK FAILED:
Calling 'greet' will never work with argument types (int) (line 1)
Expected: :(Str $name)

In addition to any of Perl 6 builtin types or user-built classes, you can also add programmatic checks inline in the signature with where clauses.

use v6;
multi odd-or-even(Int $i where * %% 2) { say "even" };
multi odd-or-even(Int $i) { say "odd"};

Note that we didn’t have to add a where clause to the second sub. The multi-dispatch algorithm will prefer the more detailed signature when it matches, but fallback to the less descriptive version when it doesn’t.

You can even use literal values as arguments. This style allows you to move some of your logic to the multi dispatcher.

use v6;
multi fib(1) { 1 }
multi fib(2) { 1 }
multi fib(Int $i) { fib($i-1) + fib($i-2) }

say fib(10);

Note that this isn’t very efficient… yet. Once the is cached trait for subs is implemented, we can use that to provide a builtin analog to Perl 5′s Memoize module.

Named

Perl 6 provides for named parameters; if the positionals are analogous to an array of parameters, the named arguments are like a hash. You use a preceding colon in the argument declaration, and then pass in a pair for each parameter when invoking the sub.

use v6;
sub doctor(:$number, :$prop) {
    say "Doctor # $number liked to play with his $prop";
}
doctor(:prop("cricket bat"), :number<5>);
doctor(:number<4>, :prop<scarf>);

Note that the order the parameters are passed in doesn’t matter for named parameters.

If you have a variable of the same name in the calling scope, you can simplify the calling syntax to avoid creating an explicit pair.

use v6;
my $prop = "fez";
my $number = 11;
doctor(:$prop, :$number)

You can also use different names internally (using the expanded pair syntax) for the named arguments. This allows you to use different (simplified or sanitized) versions for the caller.

use v6;
sub doctor(:number($incarnation), :prop($accoutrement)) {
    say "Doctor # $incarnation liked to play with his $accoutrement";
}
my $number = 2;
my $prop = "recorder";
doctor(:$number, :$prop)

Slurpy

To support functions like sprintf, we need to be able to take a variable number of arguments. We call these args “slurpy”, since they “slurp” up the arguments.

# from rakudo's src/core/Cool.pm
sub sprintf(Cool $format, *@args) {
    ...
}

When invoking this sub, the first argument must be of type Cool (kind of a utility supertype), and all the remaining positional arguments are slurped up into the @args variable. The preceding * indicates the slurp.

Symmetrically, we can also slurp up named arguments into a hash.

# from rakudo's src/core/control.pm
my &callwith := -> *@pos, *%named {
    ...
}

This snippet introduces to the pointy block syntax for anonymous subs. The -> begins the declaration, followed by the signature (without parentheses), and finally the sub definition in the block. The signature here shows all the positionals ending up in *@pos, and all the named arguments in %named.

This also shows that positionals and named arguments can be combined in the same sub.

Methods

Method and sub declarations are virtually identical. All the parameters mentioned so far are usable in methods.

The main difference is that methods can be passed an invocant (the object associated with the method call), and when you define the sub, you have the opportunity to name it. It must be the first parameter if present, and is marked with a trailing :.

use v6;
method explode ($self: $method) {...}

Note that there is no comma separating these the invocant and the first positional. In this context, the colon functions as a comma.

Parameter Traits

Each parameter can additionally specify a trait that changes the behavior of that parameter:

  • is readonly - this is the default behavior for a parameter; the sub cannot modify the passed in value.
  • is rw - the argument can be modified. Forces the argument to be required.
  • is copy - the sub gets a modifiable copy of the original value.

More Information

Check out Synopsis #6 for more information, and the roast test suite for more examples – any of the directories starting with S06-.

About these ads

2 Responses to “Day 21 – Signatures”

  1. David Houghton Says:

    So unless you specify an invocant parameter you don’t have access to the invocant in the method body? I’m a little disappointed. What’s the reasoning behind this? I would figure that any piece of code marked as a method would have access to its invocant. Does such code have access to attribute accessors but not methods?

    • lueinc Says:

      No, the invocant specifier is just to give it a more descriptive name. “self” is always a valid way to refer to the invocant. ‘method foo($bar:)’ allows you to access the invocant through $bar and self, while ‘method foo()’ just has self.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


Follow

Get every new post delivered to your Inbox.

Join 37 other followers

%d bloggers like this: