Determine if a variable in Raku is set

Viewed 415

I intentionally avoid the term defined because a variable may very well have a defined value but the .defined method will return false (Failures, for instance).

Is there any way to determine whether a variable has had a value set to it?

my $foo;
say $foo; # (Any), its type object, no value assigned
my Str $bar;
say $bar; # (Str), its type object, no value assigned
my $abc = Str;
say $abc; # (Str), the type object we assigned to $abc

How can we disinguish $bar (no value set, typed as Str) from $abc (value set to Str)?

Given that $bar.WHICH == $abc.WHICH, but $bar.VAR.WHICH !== $abc.VAR.WHICH, and methods like .defined will return false for each, is there any quick and easy way to determine that there is a set value?

I supposed it could be checked against the default value, but then there'd be no way to distinguish between the value being by virtue of unset, versus by being set in code.

5 Answers

Variables are always set to some sort of value.

If you don't set it to a value, a value will be chosen for you.
Specifically it will be set to the default.
(If you don't choose a default, it will be set to the type object.)

my $abc;

say $abc.VAR.default.raku;
# Any
my Int $def = 42;

say $def.VAR.default.raku;
# Int
my $ghi is default(42) = 2;

say $ghi.VAR.default.raku;
# 42

What you're asking for isn't really something that Raku supports.

You could probably fake something close though.
(Every instance of Mu.new is unique.)

sub is-not-set ( Mu $_ is raw ) {
  $_.self =:= $_.VAR.default
}

my $abc is default(Mu.new);
my $def is default(Mu.new) = Any;
my $ghi is default(Mu.new) = Mu.new;

say is-not-set $abc; # True
say is-not-set $def; # False
say is-not-set $ghi; # False

The thing is that assigning Nil will also set it to the default.

$def = Nil;
say is-not-set $def; # True

As will looking up the default and assigning it.

$ghi = $ghi.VAR.default;
say is-not-set $ghi; # True

I don't think you should worry about such things.

If you really really need something to happen the first time you assign to the variable, you could do something like this:

my $abc := Proxy.new(
  # this FETCH only needs to return the default
  # as this Proxy gets replaced upon the first assignment
  FETCH => -> $ { Any },

  STORE => -> $, $value {
    # replace the Proxy with a new Scalar
    $abc := do { my $abc = $value };

    say 'first assignment just happened'
  },
);

say $abc;
# Any

$abc = 1;
# first assignment just happened

say $abc;
# 1

$abc = 2;

say $abc;
# 2

The do block is just there so that $abc.VAR.name returns $abc.
Otherwise you could just write $abc := my $ = $value.

I think both the values are identical, but the containers have different type constraints.

Try

my Str $foo;
my  $bar = Str;

use Test;

cmp-ok $bar, &[===], $foo, 'The values are identical';
isa-ok $bar, Str;
isa-ok $foo, Str;
isa-ok $bar.VAR.of, Mu;
nok $bar.VAR.of.isa(Str), 'The container $bar is not of Str' ;
isa-ok $foo.VAR.of, Str;

done-testing();
ok 1 - The values are identical
ok 2 - The object is-a 'Str'
ok 3 - The object is-a 'Str'
ok 4 - The object is-a 'Mu'
ok 5 - The container $bar is not of Str
ok 6 - The object is-a 'Str'
1..6

Is that a general question or an implementation problem? If the latter, maybe (ab)using roles is an option?

role isUnset {}; 
my Str $a = Str but isUnset; 
say $a ~~ isUnset; 

# meanwile
$a = 'set'; 
# ...
$a = Str;

# and then
say $a ~~ isUnset; # Now False

my Str $bar and my $bar = Str result in the same thing, both are of type Str but have no definite values. Str is a type object, not a value.

.defined would return True if you'd give $bar a definite value, such as "Str" (note the quotes surrounding the bareword).

You might try to assign a default value to a variable, instead of keeping it undefined:

 my Str $bar is default("");

$bar will be Str only if it's assigned that value type; if its value is deleted via assigning Nil it will default again to the empty string. As a matter of fact, the default for a variable is its type object, so:

my Str $foo;
my $bar = Str;
say $foo eqv $bar

will, in fact, return True.

Related