A subroutine is a piece of code that performs a specific task. It may operate on provided data, also known as arguments. It may also produce some result, which is known as a return value. The signature of a subroutine is a description of the arguments it takes and the return value it produces.
You have already seen some simple subroutines in the first chapter. The second chapter described operators. In a sense, these are also subroutines that Perl 6 parses in interesting ways. However, these just scratch the surface of what's possible.
A subroutine declaration consists of several parts, some of which are optional. First, there is the subroutine declarator, sub
, which signifies that you are starting a subroutine declaration. The declarator may be followed by a name and/or a signature, each optional. Finally, a block of code enclosed in curly braces that is the body of the subroutine. It is this block of code that will be executed each time the subroutine is called.
Below is a simple example subroutine declaration:
By default, subroutines are lexically scoped, just like any variable declared with my
. In the absence of any code indicating otherwise, a subroutine may only be called within the scope it was declared. To make the subroutine more widely available, the scoping declarator our
may be used to also place the subroutine within the symbol table of the current package.
Perl 6 subroutines are objects. You can pass them around and store them in data structures just like you can do with any other piece of data. Programming language designers often call these first-class subroutines, because they behave like other built-in data structures. This offers tremendous potential. For example, to make a little ASCII art dancing figure, you could build up a hash where the keys are names of the dance moves, and the values are anonymous subroutines:
From the output of this program, you can observe that doing the YMCA dance in ASCII art looks as bad as in real life.
A subroutine signature performs two roles. First, it declares the arguments callers may or must pass to the subroutine. Second, it declares the variables in the subroutine to which the arguments are bound. These variables are called parameters. Perl 6 signatures go further; they allow you to constrain the values of arguments and to extract specific pieces of data structures.
In its most simple form, a signature is a comma separated list of variable names to which incoming arguments are bound.
The use of the term bound instead of assigned is significant. The variables in your signature are read-only references to the passed arguments. You cannot modify them within the subroutine. If this is too limiting, then you have two different ways to relax this restriction.
Marking a parameter as is rw
means that you are allowed to modify the passed argument. If you modify that value, you modify the original in place. If you attempt to pass a literal or some other constant value for an rw
parameter, binding of that signature will fail and the subroutine call will not occur.
If, instead, you want your own copy of the argument to work with inside the subroutine--if you want to leave the original untouched--then mark the parameter is copy
.
The extra verbosity of marking parameters as mutable may seem excessive, but it's likely you won't use these modifiers often. While certain languages require you to mark parameters as rw
to emulate returning multiple result from a single subroutine, Perl allows you to return multiple values directly.
Sigils on variables indicate their intended use. In a signature, a variable's sigil acts as a constraint on the type of argument passed. The @
sigil, for example, checks that the passed value is iterable. Failing to pass something that matches this constraint will cause the call to fail.
Similarly, the %
sigil implies that the caller must pass something that allows associative indexing through the <...>
or {...}
operations. The &
sigil requires that the caller pass something callable, such as an anonymous subroutine. In that case, you may call the callable parameter without having to use the &
sigil.
A scalar (the $
sigil) implies no constraints. Anything may bind to it, even if that anything could bind to one of the other sigils.
Sometimes you want to fill positional arguments from an array. Instead of writing eat(@food[0], @food[1], @food[2], ...)
and so on for every array item, you can interpolate them into the argument list by prepending a vertical bar: eat(|@food)
.
Likewise, you can interpolate hashes into named arguments:
Until now, the distinction between "arguments" and "parameters" has been clear. Here's where it gets confusing. We should use one consistently, or define the distinction between the two clearly for readers (if they even need to know a distinction) and use them precisely.
Sometimes parameters have sensible defaults values. Sometimes, certain arguments are unnecessary. In these cases, it is nice to mark such parameters as optional, so those calling the subroutine can choose whether to pass values.
Either assign a default value to the parameter in the signature or append a question mark to the parameter's name:
When a subroutine has many parameters, it is sometimes hard to remember their respective order. When that happens, it is often easier to call them by name instead:
The names are those that appeared as parameter names in the signature. When you pass arguments by name, the order in which they appear does not matter.
You may also specify that an incoming argument may only fill a parameter by name, never by position; precede the name of the parameter with a colon:
Named parameters are optional by default. Adding a !
at the end makes one mandatory.
Argument names do not necessarily have to correspond exactly to parameter names within the subroutine; you may remap them as you desire:
Parameters can also have multiple names. If the users are both British and Americans, one might write:
:color(:colour($c))
or :color(:$colour))
.
This section is confusing. I've tried to clarify.
Named arguments are actually Pair
s. There are multiple ways to write Pair
s; each of them provides a different mechanism for quoting. The difference between the approaches is primarily one of clarity. This allows you to pass arguments in multiple ways. These three calls all mean the same thing:
If you're only passing a boolean value, you don't even need the value portion of the pair:
A named argument of the form :name
with no value has an implicit value of Bool::True
. The negated form of this, :!name
, has an implicit value of Bool::False
.
If you use a variable to create a pair, you can reuse the variable name as the key of the pair.
The following table lists possible Pair
forms and their meanings:
Shorthand Long form Description
:allowed allowed => Bool::True Boolean flag
:!allowed allowed => Bool::False Boolean flag
:bev<tea coffee> bev => ('tee', 'coffee') List
:times[1, 3] times => [1, 3] Array
:hash{ a => 2} hash => { a => 2} Hash
:$var var => $var Scalar variable
:@var var => @var Array variable
:%var var => %var Hash variable
You can use all of these forms in any context where you can use a Pair
object, for example when populating a hash:
Finally, to pass an existing Pair
object to a subroutine by position, not name, either put it in parentheses (like (:$thing)
), or use the =>
operator with a quoted string on the left-hand side: "thing" => $thing
.
In an earlier example the function shout-it
accepted an array argument. There is no need to the user to build an array and pass that to the function:
An asterisk *
preceding an array parameter marks it as slurpy. It stores all remaining unbound positional arguments in an array. Likewise *%hash
slurps all the remaining unbound named arguments into a hash.
Slurpy arrays and hashes allow you to pass all positional and named arguments to another routine, for example:
Subroutines can also return values. The ASCII art dancing example from earlier in this chapter is prettier and simpler when its anonymous subroutines return values. Instead of modifying a variable inside the subroutine, these subroutines now each return a new string for their callers to use:
A Perl subroutine can return multiple values:
Even return
itself is not necessary. If you exclude it, Perl will return the expression produced by the last statement run inside the subroutine. This simplifies the example code:
Be wary of relying on this, however: sometimes the flow of control within a subroutine is sufficiently complex that adding explicit return
clarifies. As a general rule, only the simplest subroutines benefit from implicit return
.
return
has the additional effect of immediately exiting the subroutine:
... and you'd better not misplace your new $world
if it's temporary, as it's the only one you're going to get.
Many subroutines can not meaningfully work with arbitrary parameters, but require that the parameters support certain methods.
If that is the case, it makes sense to restrict the parameters in a way that only supported values can be passed as arguments. That way an error is raised early on (at the time of calling the routine) when a "bad" value is passed.
You can restrict the possible values that a subroutine accepts by putting a type name in front of a parameter. For example a subroutine that does numeric calculation with its parameters could restrict the parameters to the type Numeric
:
Which produces this output:
If multiple parameters have a type constraint, each argument has to fulfill the type constraint of the parameter it is bound to.
Sometimes a type name is not enough to sufficiently describe the parameters of a subroutine. In this case an additional constraint can be added to the parameter with a where
block:
Since the calculation can only be carried out for non-negative values of the area, the parameter name is followed by a where
block that returns True
for non-negative values. If such an additional constraint returns a false value, the type check at the time of the routine call fails.
TODO: explain where $value
smart matching constraints once we talk about smart matching somewhere
In one sense, a signature is a collection of parameters. Captures fill the same niche as arguments. Just as you rarely think of a signature as a whole--instead focusing on individual parameters--you rarely have to think about captures. When you do, Perl 6 allows you to manipulate captures directly. This can be useful.
Captures have both positional and named parts, which act like lists and a hashes respectively. The list-like part contains the positional parameters and the hash-like part contains the named parameters.
Sometimes you need to work with only part of an array or a hash. You can do that with ordinary slicing access, or you can use signature binding:
The signature binding approach might seem clumsy, but when you use it in the main signature of a subroutine, you get tremendous power:
The brackets in the signature tell the compiler to expect a list-like argument. Instead of binding to an array parameter, it instead gets unpacked into several parameters--in this case, a scalar for the first element and an array for the rest. This subsignature also acts as a constraint on the array parameter: the signature binding will fail unless the list in the capture contains at least one item.
Likewise you can unpack a hash by using %(...)
instead of square brackets. You must access named parameters instead of positional.
Is this a little too cute?
# TODO: come up with a good example
# TODO: generic object unpacking
Subroutines and their signatures are objects like any other. You can not only call them, but also learn things about them, especially about the parameters.
The &
sigil allows access to a subroutine without actually calling it. &logarithm.signature
returns the signature associated with the subroutine, and calling .params
on the signature returns a list of Parameter
objects.
Each of these objects describe one parameter in detail.
# TODO: talk about &signature.cando once that's implemented
# TODO: elaborate on when and why signature introspection is useful
Hey! The above document had some coding errors, which are explained below:
- Around line 1:
-
Unknown directive: =head0
- Around line 128:
-
Non-ASCII character seen before =encoding in 'order-beer('Zlatý'. Assuming UTF-8
- Around line 244:
-
=end for without matching =begin. (Stack: [empty])
- Around line 369:
-
=end for without matching =begin. (Stack: [empty])
- Around line 636:
-
=end for without matching =begin. (Stack: [empty])
- Around line 710:
-
=end for without matching =begin. (Stack: [empty])