Categories
Posts in this category
- Introduction
- Strings, Arrays, Hashes;
- Types
- Basic Control Structures
- Subroutines and Signatures
- Objects and Classes
- Contexts
- Regexes (also called "rules")
- Junctions
- Comparing and Matching
- Containers and Values
- Where we are now - an update
- Changes to Perl 5 Operators
- Laziness
- Custom Operators
- The MAIN sub
- Twigils
- Enums
- Unicode
- Scoping
- Regexes strike back
- A grammar for (pseudo) XML
- Subset Types
- The State of the implementations
- Quoting and Parsing
- The Reduction Meta Operator
- The Cross Meta Operator
- Exceptions and control exceptions
- Common Perl 6 data processing idioms
- Currying
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