Search code examples
rakusubroutinedynamic-variablesrakudo

Why can't a sub access dynamic variables when used in a "return"ed map in raku?


It seems that a a sub cannot access dynamic variables when it is used inside a map and that map is "return"ed.

Consider this piece of code:

sub start {
    my $*something = 'foobar';
    
    # WORKS
    say 'first say-something:';
    say-something;
    
    # WORKS
    say 'mapped say-something, no return:';
    my @foo = (^2).map({say-something});
    
    # ERROR: Dynamic variable $*something not found
    say 'mapped say-something, with return:';
    return (^2).map({say-something});
}

sub say-something {
    say $*something;
    1
}

start;

This will output:

first say-something:
foobar
mapped say-something, no return:
foobar
foobar
mapped say-something, with return:

Dynamic variable $*something not found
  in sub say-something at main.raku line 18
  in block <unit> at main.raku line 14

Why can't the sub access the dynamic variable? Is there a workaround for this?


Solution

  • Routine map is lazy, so the block is not run until after start has returned.

    From the documentation:

    multi method map(Hash:D \hash)
    multi method map(Iterable:D \iterable)
    multi method map(|c)
    multi method map(\SELF: &block;; :$label, :$item)
    multi sub map(&code, +values)
    

    Examples applied to lists are included here for the purpose of illustration.

    For a list, it invokes &code for each element and gathers the return values in a sequence and returns it. This happens lazily, i.e. &code is only invoked when the return values are accessed.

    So in your case, the map is evaluated once start returns, thus the dynamic variable is already out of scope.

    A workaround for this is to force the map to happen eagerly by adding .eager

    return (^2).map({say-something}).eager