Home > Raku > Defined or dynvar

Defined or dynvar

While adding dynvars to Shell::Piping to reduce the risk of finger injury I made a typo lizmat kindly corrected. She suggested to use the defined-or operator to test if a given dynamic variable is declared.

($*FOO // '') eq 'yes'

This is not equivalent to test if a dynvar was declared down the call tree. For that we need to check CALLERS.

say CALLERS::<$*colored-exceptions>:exists;
dd CALLERS::<$*colored-exceptions>;
# OUTPUT: False
#         Nil

In case the dynvar is declared we get a different result.

sub dyn-receiver {
    say CALLERS::<$*colored-exceptions>:exists;
    dd CALLERS::<$*colored-exceptions>;
}
my $*colored-exceptions;
dyn-receiver();
# OUTPUT : True
#          Any $*colored-exceptions = Any

For a module author that means we can have somebody sneak some undefined value into a dynvar we use, that has a type we don’t expect. Composebility is not the same thing as correctness. If we want do deal with this situation properly we need to check if the caller declared the dynvar and use a proper default value if they don’t.

class Switch {
    has $.name;
    method gist { $.name }
    method Str { die('invalid coersion') }
    method Bool { die('invalid coersion') }
}

constant on is export := Switch.new: :name<on>;
constant off is export := Switch.new: :name<off>;

sub dyn-receiver {
    my $*colored-exceptions = CALLERS::<$*colored-exceptions>:exists 
        ?? CALLERS::<$*colored-exceptions>
        !! off
}

In this example there are just two possible values but if there are more and they can be undefined we need to be more careful. However, this is quite a bit of typing. Can we use a deboilerplater here?

sub infix:<///>(\a, \b) is raw {
    my $dyn-name = a.VAR.name;
    my $has-outer-dynvar = CALLER::CALLERS::{$dyn-name}:exists;
    CALLER::{$dyn-name} = $has-outer-dynvar ?? CALLER::CALLERS::{$dyn-name} !! b
}

sub c {
    my $*colored-exceptions /// Int;
    dd $*colored-exceptions;
}

sub d {
    my $*colored-exceptions = Str;
    c();
}

c();
d();
# OUTPUT: Int $*colored-exceptions = Int
          Str $*colored-exceptions = Str

This operator takes two bound arguments. If we call it with a dynvar a contains the container that is the dynvar. We can query the name of that container and us it to check if down the call tree the dynvar was already declared. If so we use its value and assign it directly into the dynvar declared in c. Otherwise we assign b to the dynvar. In both cases we might return something naughty so we better do so raw.

Poking across the stack is risky. This could be done better with proper macros. I am quite sure we can do so after Christmas*.

*) For any value greater then Christmas last year.

Categories: Raku