How can I know if I actually need to return an l-value when using FALLBACK
?
I'm using return-rw
but I'd like to only use return
where possible. I want to track if I've actually modified %!attrs
or have only just read the value when FALLBACK
was called.
Or (alternate plan B) can I attach a callback or something similar to my %!attrs
to monitor for changes?
class Foo {
has %.attrs;
submethod BUILD { %!attrs{'bar'} = 'bar' }
# multi method FALLBACK(Str:D $name, *@rest) {
# say 'read-only';
# return %!attrs{$name} if %!attrs«$name»:exists;
# }
multi method FALLBACK(Str:D $name, *@rest) {
say 'read-write';
return-rw %!attrs{$name} if %!attrs«$name»:exists;
}
}
my $foo = Foo.new;
say $foo.bar;
$foo.bar = 'baz';
say $foo.bar;
This feels a bit like a X-Y question, so let's simplify the example, and see if that answers helps in your decisions.
First of all: if you return the "value" of a non-existing key in a hash, you are in fact returning a container that will auto-vivify the key in the hash when assigned to:
my %hash;
sub get($key) { return-rw %hash{$key} }
get("foo") = 42;
dd %hash; # Hash %hash = {:foo(42)}
Please note that you need to use return-rw
here to ensure the actual container is returned, rather than just the value in the container. Alternately, you can use the is raw
trait, which allows you to just set the last value:
my %hash;
sub get($key) is raw { %hash{$key} }
get("foo") = 42;
dd %hash; # Hash %hash = {:foo(42)}
Note that you should not use return
in that case, as that will still de-containerize again.
To get back to your question:
I want to track if I've actually modified
%!attrs
or have only just read the value whenFALLBACK
was called.
class Foo {
has %!attrs;
has %!unexpected;
method TWEAK() { %!attrs<bar> = 'bar' }
method FALLBACK(Str:D $name, *@rest) is raw {
if %!attrs{$name}:exists {
%!attrs{$name}
}
else {
%!unexpected{$name}++;
Any
}
}
}
This would either return the container found in the hash, or record the access to the unknown key and return an immutable Any
.
Regarding plan B, recording changes: for that you could use a Proxy object for that.
Hope this helps in your quest.
Liz's answer is full of useful info and you've accepted it but I thought the following might still be of interest.
How to know if returning an l-value ... ?
Let's start by ignoring the FALLBACK
clause.
You would have to test the value. To deal with Scalar
s, you must test the .VAR
of the value. (For non-Scalar
values the .VAR
acts like a "no op".) I think (but don't quote me) that Scalar|Array|Hash
covers all the l-value super-types:
my \value = 42; # Int is an l-value is False
my \l-value-one = $; # Scalar is an l-value is True
my \l-value-too = @; # Array is an l-value is True
say "{.VAR.^name} is an l-value is {.VAR ~~ Scalar|Array|Hash}"
for value, l-value-one, l-value-too
How to know if returning an l-value when using
FALLBACK
?
Adding "when using FALLBACK
" makes no difference to the answer.
How can I know if I actually need to return an l-value ... ?
Again, let's start by ignoring the FALLBACK
clause.
This is a completely different question than "How to know if returning an l-value ... ?". I think it's the core of your question.
Afaik, the answer is, you need to anticipate how the returned value will be used. If there's any chance it'll be used as an l-value, and you want that usage to work, then you need to return an l-value. The language/compiler can't (or at least doesn't) help you make that decision.
Consider some related scenarios:
my $baz := foo.bar;
... (100s of lines of code) ...
$baz = 42;
Unless the first line returns an l-value, the second line will fail.
But the situation is actually much more immediate than that:
routine-foo = 42;
routine-foo
is evaluated first, in its entirety, before the lhs = rhs
expression is evaluated.
Unless the compiler's resolution of the routine-foo
call somehow incorporated the fact that the very next thing to happen would be that the lhs
will be assigned to, then there would be no way for a singly or multiply dispatched routine-foo
to know whether it can safely return an r-value or must return an l-value.
And the compiler's resolution does not incorporate that. Thus, for example:
multi term:<bar> is rw { ... }
multi term:<bar> { ... }
bar = 99; # Ambiguous call to 'term:<bar>(...)'
I can imagine this one day (N years from now) being solved by a combination of allowing =
to be an overloadable operator, robust macros that allow overloading of =
being available, and routine resolution being modified so the above ambiguous call could do something equivalent to resolving to the is rw
multi. But I doubt it will actually come to pass even with N=10. Perhaps there is another way but I can't think of one at the moment.
How can I know if I actually need to return an l-value when using
FALLBACK
?
Again, adding "when using FALLBACK
" makes no difference to the answer.
I want to track if I've actually modified
%!attrs
or have only just read the value whenFALLBACK
was called.
When FALLBACK
is called it doesn't know what context it's being called in -- r-value or l-value. Any modification comes after it has already returned.
In other words, whatever solution you come up with will being nothing to do per se with FALLBACK
(even if you have to use it to implement some other aspect of whatever it is you're trying to do).
(Even if it were, I suspect trying to solve it via FALLBACK
itself would just make matters worse. One can imagine writing two FALLBACK
multis, one with an is rw
trait, but, as explained above, my imagination doesn't stretch to that making any difference any time soon, if ever, and could only happen if the above imaginary things happened (the macros etc.) and the compiler was also modified to pay attention to the two FALLBACK
multi variants, and I'm not at all meaning to suggest that that even makes sense.)
Or (alternate plan B) can I attach a callback or something similar to my
%!attrs
to monitor for changes?
As Lizmat notes, that's the realm of Proxy
s. And thus your next SO question... :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With