Skip to content

Latest commit

 

History

History
867 lines (577 loc) · 22.6 KB

subs-n-sigs.pod

File metadata and controls

867 lines (577 loc) · 22.6 KB

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.

Declaring A Subroutine

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.

Adding Signatures

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.

The Basics

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.

Passing Arrays, Hashes and Code

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.

Interpolating Arrays and Hashes

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:

Optional Parameters

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:

Named Parameters

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.

Renaming Parameters

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

Alternative Named Argument Syntaxes

This section is confusing. I've tried to clarify.

Named arguments are actually Pairs. There are multiple ways to write Pairs; 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.

Slurpy Parameters

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:

Returning Results

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.

Working With Types

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.

Basic Types

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.

Adding Constraints

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

Captures

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.

Creating And Using A Capture

Captures In Signatures

Unpacking

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

Introspection

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

POD ERRORS

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