Defined vs. exists with Perl6 hash keys
if defined %hash{'key'} {
%hash{'key'}++;
} else {
%hash{'key'} = 1;
}
Use the defined
routine or method. See 5to6-perlfunc -- defined
defined
is a property of a value.
exists
is a variation selector of the hash indexing operation.
This is true in both Perl5 and Perl6.
It's just that they way they handle it is different.
Say you have a hash like this:
my %h = ( a => 1, b => 2, c => Int );
It has three keys:
say %h.keys; # (a b c)
You can get the value associated with a key:
my $value = %h{'a'};
You can ask the value if it was defined
say defined $value; # True
Now let's try it with c
:
my $value = %h{'c'};
say defined $value; # False
And with D
:
my $value = %h{'D'};
say defined $value; # False
Note that even though D
isn't a key in the hash, the value you get back looks the same as as looking up c
.
The value doesn't know how you got ahold of it. It has no clue about the hash indexing operation.
It is the same even if you don't store it in a variable.
say defined %h{'c'}; # False
say defined %h{'D'}; # False
So we have to tell the indexing operation instead to give us that information.
my $exists = %h{'D'}:exists;
say $exists; # False
Also if you use the result of %h{'D'}
as a container, it [magically] starts to exist.
say %h{'D'}:exists; # False
say %h{'D'}.perl; # Any
say %h{'D'}:exists; # False
%h{'D'}++;
say %h{'D'}:exists; # True
say %h{'D'}; # 1
So then why is it different than Perl5?
In Perl5 exists
and defined
are keywords. In Perl6 they are not.
They are not special in any way, shape, or form in Perl6.
Here is the Perl5 version for checking exists
:
use v5.12.0;
use warnings;
my %h = ( a => 1, b => 2, c => undef );
my $exists = exists $h{D};
say $exists ? 'True' : 'False'; # False
If you call the compiler with -MO=Concise
you will get a listing of the opcodes that it would execute.
Here is just the part that shows that exists
is an opcode:
…
d <;> nextstate(main 5 -e:1) v:%,us,*,&,{,$,fea=2 ->e
g <2> sassign vKS/2 ->h # <----- =
- <1> ex-exists sK/1 ->f # <-\
e <+> multideref($h{"D"}) sK/EXISTS ->f # <--+-- exists $h{D}
- <0> ex-padhv sR ->e # <-/
f <0> padsv[$exists:5,6] sRM*/LVINTRO ->g # <----- $exists
h <;> nextstate(main 6 -e:1) v:%,us,*,&,{,$,fea=2 ->i
…
The multideref($h{"D"})
is marked with EXISTS
, and there is ex-exists
opcode.
The listing for defined
is very similar.
One of the design goals of Perl6 was to have as few special cases as possible.
This is why neither exists
or defined
are keywords.
If you look for where defined
is in the Rakudo codebase you find this:
proto sub defined(Mu, *%) is pure {*}
multi sub defined(Mu \x) { x.defined }
defined
is just a subroutine that takes one value, and calls the .defined
method on that value.
Since it does a method call, the value gets a say on whether it is considered to be defined or not.
The default that all values inherit is in Mu.defined source:
proto method defined(|) {*}
multi method defined(Mu:U: --> False) { }
multi method defined(Mu:D: --> True) { }
So the default is for the type object to be undefined, and an instance to be defined.
One notable exception is the Failure object:
multi method defined(Failure:D: --> False) { $!handled = 1 }
So that makes instances of a Failure object to be seen as not defined.
It will also cause the Failure to consider itself handled.
(If a Failure isn't handled it will throw a warning when it gets garbage collected.)
So what about :exists
on %h{'D'}
?
In Perl6 the “normal” operators are defined as subroutines with a special name.
sub foo (%hash, $key){}
say &foo.signature.perl;
# :(%hash, $key)
say &postcircumfix:<{ }>.signature.perl;
# :($, $?, Mu $?, *%)
Note that the postcircumfix operator { }
is backed by several multi-subs, but we really only need to look at one of them.
multi sub postcircumfix:<{ }>( \SELF, \key, Bool() :$exists!, *%other ) is raw {
SLICE_ONE_HASH( SELF, key, 'exists', $exists, %other )
}
You can ignore most of that. The key thing to pay attention to is :$exists!
.
By default named arguments are optional, and don't get a say in which multi gets selected.
In order for :exists
to force this one to be selected it must be marked as required !
.
Whenever you see :exists
you can think of it as short for :exists(True)
and exists => True
.
(Also :$exists
is short for :exists($exists)
and exists => $exists
.)
So both of these lines are functionally identical:
my $exists = %h{'D'}:exists;
my $exists = postcircumfix:<{ }> %h, 'D', exists => True;
It tells the operator (subroutine) to use the exists
variation.
Note that it also passes along the value of $exists
, which means it can do inverted exists checks.
say %h{'c'}:exists; # True
say %h{'c'}:!exists; # False
say %h{'D'}:exists; # False
say %h{'D'}:!exists; # True
:!exists
is short for :exists(False)
and exists => False
.
So the reason there isn't :defined
variation of postcircumfix { }
is that there doesn't need to be one. Just ask the resulting value if it was defined.
The exact code you've got can be done much simpler in Perl 6, because of autovivification:
my %foo;
%foo<thekey>++;
say %foo.perl; # => {:thekey(1)}
Other than that, the difference between defined %foo<thekey>
and %foo<thekey>:exists
is that you can put an undefined value into the hash at the given key and it'll answer True
to :exists
but False
to defined
.