Sun, 21 Sep 2008

Subroutines and Signatures


Permanent link

NAME

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

LAST UPDATED

2015-02-25

SYNOPSIS

    # 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")

DESCRIPTION

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) { 
        ...
    }

Named Parameters

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.

Slurpy Parameters

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\nRest: 2 3 4\n"

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);

Interpolation

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)

Multi Subs

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

MOTIVATION

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.

SEE ALSO

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

[/perl-5-to-6] Permanent link