[Perl 6 page]

return

In previous versions of Perl, you know that functions can return more than one value at a time. A good example is ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);. Furthermore, a function can tell whether the caller is expecting a single item or a list, by using the wantarray function.

In Perl 6, returns are even more complex. The caller can decide whether a list should be flattened, and you can have named returns. The localtime function would certainly be easier if you didn’t have to remember the correct order and only needed to mention the ones you needed: (:$year,:$mon,:$mday) = localtime(time);

All functions return a Capture

The basic idea is that all functions actually return a Capture object. This is the same mechanism that is used to pass arguments to functions, and explains how named returns work. But normally a function return is not used in any manner that resembles a function call, so there are some special rules going on. This article explains all that.

A Capture holds any number of values, possibly with names, and other context information. Clearly rewriting a function invocation with a Capture in its place is not what we see. The Capture is usually implicitly dereferenced, so the function rewrite puts what was inside the Capture, not the Capture itself.

sub foo () { return 2+2; }
$x = foo;
# you think the invocation becomes
$x = 4;
# but it actually becomes
my Capture $R = \( 4 );
$x = $( $R );

That is, all functions return a Capture, with one or more values inside. And then that Capture is automatically dereferenced, as shown with the prefix $() notation.

But, clearly you don’t want all Captures to automatically dereference. If you explicitly write something like:

$z = \($x,$y);
$q = $z;

You don’t expect the Capture object to spontaneously fall apart, but rather $q is assigned the object itself, like any other object. This self-exploding behavior is limited to function returns. So, it is easily explained by saying that all functions return a ReturnCapture object. ReturnCapture is derived from Capture, and they are mostly invisible. Just like an Item container holds one item and (almost) any operation you perform on it really goes to the thing in the container, not the container itself, so a ReturnCapture is a container with similar powers of invisibility.

A ReturnCapture will automatically dereference itself with $(), @(), or @@() depending on the context, for almost every operation performed on it. So, you normally don’t notice them when you call a function and use the result in a larger expression.

You do see the ReturnCapture object if you use it on the right-hand side of the := (binding) operation. This lets you use the full power of function calling parameter passing on the return side of things.

sub foo ()
 {
  return x=>3, y=>7, z=>1;
 }

my ($z, $y) := foo;

|foo --> $x, $z { ... }

You also see the actual ReturnCapture object if you use the prefix:<|> operator. This operates directly on the ReturnCapture, and does not fall through to apply to its contents, and can be used to interpolate the ReturnCapture’s contents into the current capture context. But, if you are not in a capture context, | will cast the ReturnCapture to a regular Capture, so it becomes fully visible.

my Capture $cap = |foo;

Third, list assignment will handle assignment to a literal pair by accessing the names of the items inside the Capture. So list assignment behaves a lot like binding, but doesn’t do everything that parameter passing can do.

my $a, $x, $z;
(:z<$z>, :y($a)) = foo;

Recall that :$z is shorthand for :z<$z>, and you see why the first example works.

Finally, capturing the result of a function call logically re-captures the exploded capture, so it gives the same Capture object. It would be confusing and ambiguous to have \foo mean one of \( $ ( foo )), \( @ ( foo )), or \( @@ ( foo )). So it means \( |foo ).

Summary

Functions always return a Capture. The reason this doesn’t get in the way is because it is normally dereferenced to Item, List, or Slice context, automatically.

The only operations that operate directly on the returned Capture object are:

Declaring complex return signatures

How do you declare simple single-value returns? There are three different syntaxes for it:

our Int foo (Int $a, Int $b) { ... }
foo (Int $a, Int $b --> Int) { ... }
foo (Int $a, Int $b) returns Int { ... }

You can put the return type before the function name, but only if you have a my or our keyword first. You can put the return type in the parameter list using -->. Or you can use the returns property. The last one is the most fundamental—the other two are syntactic sugar for this, which best represents what is really going on. The return signature is a property of the Callable object.

Let’s take a brief excursion to look at the syntax for local variables. The full-blown syntax is that the keyword my is followed by a Signature. But, you can leave off the colon, and even the parentheses if you like:

my :($x);  # canonical form
my ($x);  # shorthand
my $x;  #  even more abbreviated, but same thing.

The same grammar is used for the argument of returns. It actually takes a single Signature object, but you can leave off the delimiters as long as the parser is not confused.

foo (Int $a, Int $a)  returns Int { ... }
# is just short for
foo (Int $a, Int $b)  returns :(Int)  {...}
# and you could declare
bar (Int $a, Int $b)  returns :(Int $month, Int $year) { ... }

That is, you can do anything that you could do in a parameter list, including named arguments and even default values!

The marker --> is used to separate the parameter list into two lists: the incoming parameters and the return parameters. The compiler just takes everything after the --> and generates the returns property from that. So you could write:

bar (Int $a, Int $b)  returns :(Int $month, Int $year) { ... }
# is the same as
bar (Int $a, Int $b --> Int $month, Int $year) { ... }

Since the list is chopped into two Signature objects, you can see that the names of the return parameters are not related to the names of the incoming parameters in any way. They don’t make local variables, or pick up defaults from same-named values, or anything like that.

What about the first form, where the return type comes before the function name? It is the same way, but don’t expect the abbreviated forms to work except in the simplest cases.

our :(Int $month, Int $year) bar (Int $a, Int $b)  { ... }

Passing values out of the function

The return keyword is parsed just like a function call. The arguments are stored in a Capture and shipped off to the other side. So, there is symmetry in outgoing values and incoming values. You can think of return as a function whose signature is the return signature you declared.

So, think about a function call passing arguments:

my argtest (Int $x, Str $y = "", Bool :$flag) { ... }

argtest (1);
argtest (1, "Smith");
argtest (1, :flag);
argtest (1, "Smith", :flag);
argtest (:flag, :y<Smith>, :x(1));
argtest (:flag, :y<Smith>, 1);

There are rules for taking the stuff in the argument list and matching them up with the things declared in the Signature. So, you can call the function many different ways, meaning you can use different syntax or ordering to specify the Capture that gets shipped off to the function.

Well, the same thing happens when you specify the Capture that gets shipped back to the caller. All the arguments of the return get matched up with what was declared in the return signature. (Yes, ret-test is a legal identifier!)

my ret-test ($n) returns :(Int $x, Str $y = "", Bool :$flag)
 {
  PRE { $n ~~ 1..6 }  # precondition to check argument
  return (1)    if $n==1;
  return (1, "Smith")    if $n==2;
  return (1, :flag)    if $n==3;
  return (1, "Smith", :flag)    if $n==4;
  return (:flag, :y<Smith>, :x(1))    if $n==5;
  return (:flag, :y<Smith>, 1)    if $n==6;
 }

With either calling or returning, you can leave off the outermost parentheses. The syntax for return is exactly the same as for calling a function. In fact, the return keyword is just syntactic sugar for &?ROUTINE.return(1); which is a method call, like any other.

Accepting values returned to you

If you use the binding syntax (:=) and declare some variables, then the return capture is bound to the declared variables in exactly the same way as arguments are bound to the local variables declared as the parameters in a function call.

my (Str $y, Int $x) := ret-test(5);

The Capture passed to the return statement will be used to bind the my variables, in exactly the same way as a Capture passed to a function call will be used to bind the local parameter variables.

However, there is an intermediate step. The Capture passed to the return is first used to create a new Capture based on the return signature. This is important, not just because of the type coercions and default arguments, but to ensure that the return signature is a contract that the caller can depend on. (Yes, ret-test' is a legal identifier!)

# Without a return signature, the caller would see different results
# depending on the way the return statement was written.
# Consider ret-test' which is the same as ret-test but without the declared returns.
my (Str $y, Int $x) := ret-test'(5);  # y=>'Smith', x=>1, so OK
my (Str $y, Int $x) := ret-test'(4);  # ($y,$x) gets (1,'Smith'), WRONG

# But the caller can depend on the behavior promised by the return signature,
# regardless of how the return statement is written.
my (Str $y, Int $x) := ret-test(5);  # ($y,$x) gets (1,'Smith'), WRONG
my (Str $y, Int $x) := ret-test(4);  # ($y,$x) gets (1,'Smith'), WRONG

# The first two return values are positional, so the names don’t matter
# even when using := for binding.  The function behaves consistently according
# to what is declared, regardless of how the return statement is written.

my (Int $var1, Str $var2) := ret-test(whatever);  # var names don’t matter for positional returns.

The caller of the function, reading the return signature, will only see x and y being positional. The names are for documentation only or the convenience of the function returning them, but those names are not present in the Capture.

A similar capability exists when using list assignment. If you did not want to declare the variables right there, but use existing variables, you could write:

($x, $y) = ret-test(2);
($x, $y, :flag<$verbose>) = ret-test(3);
($x, $y) =  ret-test(5);

Again, the third line works as expected, having read only the function declaration. The Capture returned is \( 1, 'Smith', flag=>True ), and not \( flag=>True, y=>'Smith', x=>1 ). The original Capture used in the return statement is transformed into a standard one based on the function’s return signature.

The example seen early on of (:$year,:$mon,:$mday) = localtime(time); can be made to work by returning both positional and named values. The return signature would need to have twice as many items, and the return statement would need to supply each one twice.

...  returns :(
   # Positional values.  Names are to remind you what order they are in, only.
   Int $sec_, Int $min_, Int $hour_, 
   Int $mday_, Int $mon_, Int $year_, 
   Int $wday_, Int $yday_, Bool $_isdst,
   # And a full set of named values.
   Int :$sec, Int :$min, Int :$hour, 
   Int :$mday, Int :$mon, Int :$year, 
   Int :$wday, Int :$yday, Bool :$isdst
   ) ...

 return 48, 30, 2, 9, 6, 108, 6, 222, True, 
    sec=>48, min=>30, hour=>2, mday=>9, mon=>6, year=>108, wday=>6, yday=>222, isdst=>True;

Positional values in the signature only give you positional values out. Named values in the signature will give you only named values out. The two never cross over, even though the return statement can supply positional values by position or by name, the caller doesn’t see that.

Of course, writing a function like this is not recommended, except to provide compatibility with old code as in this case.

Whether values are returned by position or by name is a decision that needs to be carefully considered. In a function like localtime, it might be best to force the caller to use names, because that is less error prone. Returning named values can be handy to pipeline the whole thing to an argument list of another function call. But if all the values are named, you can’t use the function in the normal way as just seeing the contents of the Capture in Item or List context, because both of those contexts work with the positional values.

sub foo ($n) returns :( :$x, :$y )
 {
  return x=>$n*3, y=>$n/4;
 }

sub bar ($x, $y) { ... }

my $result= 2*foo(3);  # undef !  No positional return
bar(|foo(3));  # How it’s designed to be used
(:x<$result>) = foo(3);  # how to get a value out of it
$result *= 2;

If you have multiple positional parameters, it is easy to use or ignore them in the same manner as a single return value.

$x = sin($angle);  # expected use
sin($angle);  # ignore result, but not an error nor warning

sub foo ($angle --> $,$) { ... }
($main, $aux) = foo($angle);  # use both values
$main = foo($angle);  # use only first value
foo($angle);  # use none of the return values

Orthodoxy

“All functions return a Capture” is according to the Synopses. What is missing is a detailed explanation of how that works. The first section of this article fills in the details and is completely consistent with the Synopses.

“Declaring complex return signatures” is never explained in the Synopses. I extrapolated, and glossed over details that would require detailed study of the grammar; in particular, how much each form can be abbreviated.

“Passing values out of the function” is exactly as explained in the Synopses.

“Accepting values returned to you” needed some fleshing out based on what was in the Synopses. In particular, the way the stated Capture interacts with the declared return signature is not mentioned. My examples show why it needs to work the way I proposed: the return signature is a contract with the caller. (Note that I did not cover the detailed rules or the presence of a separate as signature. That will have to be explained in a formal proposal.)

† When I was writing this, Larry checked in a change to allow - and ' in identifier names, if followed by a letter. I think it would be very useful to allow ' as a terminal, as shown here. Using it to mean “prime” seems to be a common thought.