10

What is the syntax to return a value from a CATCH phaser from a block which is not a Routine?

sub foo() {
    <1 2 3>.map: -> $a {
        die 'oops';
        CATCH { default { 'foo' } }
    }
}

sub bar() {
    <1 2 3>.map: -> $a {
        die 'oops';
        CATCH { default { return 'bar' } }
    }
}

say foo(); # (Nil, Nil, Nil)
say bar(); # Attempt to return outside of immediatelly-enclosing Routine (i.e. `return` execution is outside the dynamic scope of the Routine where `return` was used)

edit: Desired output is:

say baz(); # (baz baz baz)

The use case is maping a Seq with a method which intermittently throws an exception, handling the exception within the block passed to map by returning a default value.

Pat
  • 36,282
  • 18
  • 72
  • 87
J Hall
  • 531
  • 3
  • 10
  • I'd like `say baz()` to return '(baz baz baz)'. The use case I had in mind was to map a Seq to a method that may intermittently fail, but CATCH that exception and map to a default value. – J Hall Apr 19 '18 at 00:13

2 Answers2

9

Return exits out of a function scope, but the way you are using it in bar() there are two functions at play.

  1. The bar() method itself.
  2. The lambda you embedded the return within.

This means that your return is ambiguous (well, at least to some people) and the compiler will balk.

Without the "return" the value in foo() is handled as a constant within the block, and the block returns Nil. This means that in foo() you effectively avoided parsing the meaning of return, effectively pushing a Nil on the stack.

That's why you have 3 Nils in the captured output for foo(). For bar() it is unclear if you wished to terminate the execution of the bar() routine on the first thrown exception or if you just wanted to pass 'bar' back as the non-Nil value the CATCH block pushed onto the stack.

The slightly modified version of your code

#!/bin/env perl6

sub foo() {
    <1 2 3>.map: -> $a {
        die 'oops';
    }
    CATCH { default { 'foo' } }
}

sub bar() {
    <1 2 3>.map: -> $a {
        die 'oops';
    }
    CATCH { default { return 'bar' } }
}

say foo();

say bar();

might make this a bit more clear. It's output is

[edwbuck@phoenix learn_ruby]$ ./ed.p6 
Nil
bar
Christopher Bottoms
  • 11,218
  • 8
  • 50
  • 99
Edwin Buck
  • 69,361
  • 7
  • 100
  • 138
  • 1
    I think your reasoning for why your version of the code works is faulty. The reason why `Nil` is the return value of the first code is that `CATCH` blocks always evaluate to `Nil`. The return value of the function is always going to be `Nil` for that reason, even if no `die` happens. Instead, the reason why the `CATCH` outside the lambda catches an exception at all is that the result of the `map` call is written in `sink` context (aka `void` context) which causes the full list to be evaluated for side-effects immediately, causing the exception to be thrown and `CATCH` to be run. – timotimo Apr 18 '18 at 23:30
  • I was looking at how I could return 'foo'x3 or 'bar'x3. The usecase I was trying to snippet wasn't an always die'ing lambda, but a map wrapping an intermittently failing call for which I'd like to map to a default value. I'll edit the question for that case. – J Hall Apr 19 '18 at 00:10
3

What's happening here is that lazy lists are causing the control flow to be non-obvious.

The return value of both of your functions is a Seq whose values get generated by calling the little lambda on the values a, b, and c in turn. Knowing this, it is easy to see why you can't change the return value of bar: bar has already returned before your lambda even gets called for the first time. Once the list is sayd, all values are generated and the exception is thrown.

The correct way to get what you want is to call .eager on the result of your map, thus causing the whole list to be evaluated before the function returns, which allows you to use return to change the value that bar returns.

timotimo
  • 4,299
  • 19
  • 23