Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I return from an anonymous recursive sub in perl6?

Tags:

raku

This does what I'd expect. fib(13) returns 233.

sub fib(Int $a --> Int) {
    return 0 if $a == 0;
    return 1 if $a == 1;

    return fib($a -1) + fib($a -2);
}

my $square = -> $x { $x * 2 };   # this works with no return value
my @list = <1 2 3 4 5 6 7 8 9>.map( $square );
# returns [2 4 6 8 10 12 14 16 18]

I tried implementing fib() using an anonymous sub

my $fib = -> Int $x --> Int {
    return 0 if $x == 0;
    return 1 if $x == 1;
    return $fib($x - 1) + $fib($x - 2); 
}

$fib(13) 

I get the following error when running that with explicit returns.

Attempt to return outside of any Routine in block at test.p6 line 39

So I got rid of the return values.

my $fib = -> Int $x --> Int {
    0 if $x == 0;
    1 if $x == 1;
    $fib($x - 1) + $fib($x - 2); 
}

say $fib(13);

This last version never returns. Is there a way to write an anonymous recursive function without return values?

like image 756
jmcneirney Avatar asked Feb 09 '19 19:02

jmcneirney


Video Answer


3 Answers

According to the documentation :

Blocks that aren't of type Routine (which is a subclass of Block) are transparent to return.

sub f() {
say <a b c>.map: { return 42 };
               #   ^^^^^^   exits &f, not just the block  }

The last statement is the implicit return value of the block

So you can try:

my $fib = -> Int $x --> Int {
    if ( $x == 0 ) {
        0;  # <-- Implicit return value
    }
    elsif ( $x == 1 ) {
        1;  # <-- Implicit return value
    }
    else {
        $fib($x - 1) + $fib($x - 2);  # <-- Implicit return value
    }
}
like image 125
Håkon Hægland Avatar answered Oct 08 '22 08:10

Håkon Hægland


Three more options:

sub

You can write anonymous routines by using sub without a name:

my $fib = sub (Int $x --> Int) {
  return 0 if $x == 0;
  return 1 if $x == 1;
  return $fib($x - 1) + $fib($x - 2); 
}

say $fib(13); # 233

See @HåkonHægland's answer for why this (deliberately) doesn't work with non-routine blocks.

leave

The design anticipated your question:

my $fib = -> Int $x --> Int {
  leave 0 if $x == 0;
  leave 1 if $x == 1;
  leave $fib($x - 1) + $fib($x - 2); 
}

compiles. Hopefully you can guess that what it does -- or rather is supposed to do -- is exactly what you wanted to do.

Unfortunately, if you follow the above with:

say $fib(13);

You get a run-time error "leave not yet implemented".

My guess is that this'll get implemented some time in the next few years and the "Attempt to return outside of any Routine" error message will then mention leave. But implementing it has very low priority because it's easy to write sub as above, or write code as @HåkonHægland did, or use a case/switch statement construct as follows, and that's plenty good enough for now.

case/switch (when/default)

You can specify the parameter as $_ instead of $x and then you're all set to use constructs that refer to the topic:

my $fib = -> Int $_ --> Int {
  when 0 { 0 }
  when 1 { 1 }
  $fib($_ - 1) + $fib($_ - 2)
}

say $fib(13); # 233

See when.

like image 34
raiph Avatar answered Oct 08 '22 08:10

raiph


Blocks don't need to declare the return type. You can still return whatever you want, though. The problem is not in using return, it's in the declaration of the Int.

use v6;

my $fib = -> Int $x  {
    if $x == 0 {
        0;
    } elsif $x == 1 {
        1;
    } else {
        $fib($x - 1) + $fib($x - 2);
    }
}

say $fib(13) ;

The problem is that the return value needs to be the last executed. In the way you have done it, if it finds 0 or 1 it keeps running, getting to the last statement, when it will start all over again. Alternatively, you can use given instead of the cascaded ifs. As long as whatever it returns is the last issued, it's OK.

like image 35
jjmerelo Avatar answered Oct 08 '22 10:10

jjmerelo