"Perl 5 to 6" Lesson 04 - Subroutines and Signatures

2015-02-25

# sub without a signature - perl 5 like sub print_arguments { say "Arguments:"; for @_ { say "\t$_"; } } # Signature with fixed arity and type: sub distance(Int $x1, Int $y1, Int $x2, Int $y2) { return sqrt ($x2-$x1)**2 + ($y2-$y1)**2; } say distance(3, 5, 0, 1); # Default arguments sub logarithm($num, $base = 2.7183) { return log($num) / log($base) } say logarithm(4); # uses default second argument say logarithm(4, 2); # explicit second argument # named arguments sub doit(:$when, :$what) { say "doing $what at $when"; } doit(what => 'stuff', when => 'once'); # 'doing stuff at once' doit(:when<noon>, :what('more stuff')); # 'doing more stuff at noon' # illegal: doit("stuff", "now")

Subroutines are declared with the sub keyword, and can have a list of formal parameters, just like in C, Java and most other languages. Optionally these parameters can have type constraints.

Parameters are read-only by default. That can be changed with so-called "traits":

sub try-to-reset($bar) { $bar = 2; # forbidden } my $x = 2; sub reset($bar is rw) { $bar = 0; # allowed } reset($x); say $x; # 0 sub quox($bar is copy){ $bar = 3; } quox($x); say $x # still 0

Parameters can be made optional by adding a question mark ? after them, or by supplying a default value.

sub foo($x, $y?) { if $y.defined { say "Second parameter was supplied and defined"; } } sub bar($x, $y = 2 * $x) { ... }

When you invoke a subroutine like this: my_sub($first, $second) the $first argument is bound to the first formal parameter, the $second argument to the second parameter etc., which is why they are called "positional".

Sometimes it's easier to remember names than numbers, which is why Perl 6 also has named parameters:

my $r = Rectangle.new( x => 100, y => 200, height => 23, width => 42, color => 'black' );

When you see something like this, you immediately know what the specific arguments mean.

To define a named parameter, you simply put a colon : before the parameter in the signature list:

sub area(:$width, :$height) { return $width * $height; } area(width => 2, height => 3); area(height => 3, width => 2 ); # the same area(:height(3), :width(2)); # the same

The last example uses the so-called colon pair syntax. Leaving off the value results in the value being True , and putting a negation in front of the name results in the value being False :

:draw-perimeter # same as "draw-perimeter => True" :!transparent # same as "transparent => False"

In the declaration of named parameters, the variable name is also used as the name of the parameter. You can use a different name, though:

sub area(:width($w), :height($h)){ return $w * $h; } area(width => 2, height => 3);

Named parameters are optional by default, so the proper way to write the sub above would be

sub area(:$width!, :$height!) { return $width * $height; }

The bang ! after the parameter name makes it mandatory.

Just because you give your sub a signature doesn't mean you have to know the number of arguments in advance. You can define so-called slurpy parameters (after all the regular ones) which use up any remaining arguments:

sub tail ($first, *@rest){ say "First: $first"; say "Rest: @rest[]"; } tail(1, 2, 3, 4); # "First: 1

Rest: 2 3 4

"

Named slurpy parameters are declared by using an asterisk in front of a hash parameter:

sub order-meal($name, *%extras) { say "I'd like some $name, but with a few modifications:"; say %extras.keys.join(', '); } order-meal('beef steak', :vegetarian, :well-done);

By default arrays aren't interpolated in argument lists, so unlike in Perl 5 you can write something like this:

sub a($scalar1, @list, $scalar2) { say $scalar2; } my @list = "foo", "bar"; a(1, @list, 2); # 2

That also means that by default you can't use a list as an argument list:

my @indexes = 1, 4; say "abc".substr(@indexes) # doesn't do what you want

(What actually happens is that the first argument is supposed to be an Int , and is coerced to an Int. Which is the same as if you had written "abc."substr(@indexes.elems) in the first place).

You can achieve the desired behavior with a prefix |

say "abcdefgh".substr(|@indexes) # bcde, same as "abcdefgh".substr(1, 4)

You can actually define multiple subs with the same name but with different parameter lists:

multi sub my_substr($str) { ... } # 1 multi sub my_substr($str, $start) { ... } # 2 multi sub my_substr($str, $start, $end) { ... } # 3 multi sub my_substr($str, $start, $end, $subst) { ... } # 4

Now whenever you call such a sub, the one with the matching parameter list will be chosen.

The multis don't have to differ in the arity (ie number of arguments), they can also differ in the type of the parameters:

multi sub frob(Str $s) { say "Frobbing String $s" } multi sub frob(Int $i) { say "Frobbing Integer $i" } frob("x"); # Frobbing String x frob(2); # Frobbing Integer 2

Nobody will doubt the usefulness of explicit sub signatures: less typing, less duplicate argument checks, and more self-documenting code. The value of named parameters has also been discussed already.

It also allows useful introspection. For example when you pass a block or a subroutine to Array.sort , and that piece of code expects exactly one argument, a Schwartzian Transform (see http://en.wikipedia.org/wiki/Schwartzian_transform) is automatically done for you - such a functionality would be impossible in Perl 5, because the lack of explicit signatures means that sort can never find out how many arguments the code block expects.

Multi subs are very useful because they allow builtins to be overridden for new types. Let's assume you want a version of Perl 6 which is localized to handle Turkish strings correctly, which have unusual rules for case conversions.

Instead of modifying the language, you can just introduce a new type TurkishStr , and add multi subs for the builtin functions:

multi uc(TurkishStr $s) { ... }

Now all you have to do is to take care that your strings have the type that corresponds to their language, and then you can use uc just like the normal builtin function.

Since operators are also subs, these refinements work for operators too.

http://design.perl6.org/S06.html, http://doc.perl6.org/language/functions