On 2010-08-02, at 2:35 pm, TSa (Thomas Sandlaß) wrote:
> On Monday, 2. August 2010 20:02:40 Mark J. Reed wrote:
>> [...] it's at least surprising. I'd expect (anything ~~ True) to be
>> synonymous with ?(anything)
> Note also that ($anything ~~ foo()) just throws away $anything.
No; only if foo() returns a Bool. Do check the section on Smart-Matching in
S03 and Switch Statements in S04; the specced behaviour isn't just a mistake.
There are only two cases where ~~ ignores the LHS: one is a block that takes no
args. A block that takes one arg runs foo($_) (and of course if it takes more
args, that's an error), but if the block can't take any args, how would you
apply $_ anyway? If you want to compare $_ against its result, then you should
call the block instead of using the Callable code object directly.
> Could someone please give a rational for the interspersing technique
> given $anything { when $one {...} when two() {...} when $three {...} }
> where two() returning a true value prevents the case $three.
That's the other case that ignores $_: not merely returning a "true" value, but
a Bool (whether that Bool is true or false). Trying to match a literal True or
False will warn you, so even if you're surprised by it, you won't be caught
unaware. If that's what you really want to do, say ?* or !* (which even
happens to be shorter than 'True' or 'False'!).
The rationale for ignoring the topic with a non-literal Bool is that "when 3<4"
will tempt people to read that like "if 3<4". (Human beings don't interpret
context rigidly enough for the Principle of Least Surprise to be consistent!)
Saying "when /foo/" or "when bigger-than(4)" obviously call for something to
compare against, i.e. the topic, but "3<4" or "foo() < $bar" *look* like
complete conditionals. So people will get it wrong occasionally no matter
which way it's specced.
However, it's arguably more useful to accept conditions in 'when' without
comparing the result to $_ because that is surely the more common case. If you
really want something like "?$_ == (foo<bar)" then you can say that — since the
result of that expression is itself boolean, "when ?$_ == (foo<bar)" will do
the right thing. (Instead of really doing $_ ~~ (?$_ == (foo<bar)), which is
almost definitely the wrong thing!)
On the other hand, if you do want to see whether $_ is true or not, you'll
probably be using "if". Given/when is particularly useful if you have a long
list of comparisons, so you don't need to repeat a possibly-long topic every
time, or even to repeat the "$_ ~~" each time. But with "if" you only need to
write the "big-long($something.whatever) ~~" once anyway, and the only other
possibility is simply "else".
Now, this does mean that the special case for Bool is best suited for use with
'when'. You're unlikely to expect $foo ~~ (3<4) to ignore the LHS, I think,
simply because if that's what you meant, you would just leave off the "$foo~~".
You can't do that with a 'when', which is what makes it a useful shortcut
there. Maybe such a case could raise a warning. Or why not have the special
case apply only to "when" and not to ~~ in general?
Anyway, given an example like Aaron's:
if !$dishes-status && $dishes-status ~~ $trash-status {...}
you would use == instead of ~~, and then there's no problem. Or more likely,
write "!$dishes && !$trash" or even "none($dishes, $trash)". A warning here
would certainly help catch cases where you want == or === instead of ~~.
> given $anything { when $one {...} if two() {...} else { when $three {...} }
> }
> still smart match $anything with $three? Or is the topic the return value of
> two() at that point?
No, if two() returns false, then the 'else' is executed with the original
topic. (Well, unless two() sneakily changed its parent $_, which, being Perl,
is possible.)
Now it seems Rakudo breaks out of the 'else' block when $three is true, but
then continues with the rest of the 'given' block — I think that's a bug,
because it ought to break out of the scope that took $_ as a param, i.e. it
should break directly out of the 'given'.
> I would opt for a regularization of the smart match table. First of all the
> generic scalar rule should coerce the LHS to the type of the RHS and then
> use === on them.
And that's close to what it does, except when the RHS is something
"indeterminate" like a regex or a range or a type. The Any-Any case just does
$_ === $X, which instead could arguably cast $_ to the type of $X first. That
might be quite useful, especially if there's a way to tell 'when' to use
another operator (like plain ===) when you need it.
Apart from that, the Bool cases have been optimised for ordinary use in typical
given/when blocks (as opposed to staring at the smart-match table and saying,
Why isn't it more consistent??). They are more obviously exceptional used
directly with ~~, but also less likely to occur (although to some extent this
does rely on having a habit of using ? or == to check boolean conditionals).
Despite that, it still rubs me the wrong way a little bit. (I guess it's like
a picture hanging crooked on the wall — it's not actually hurting anything, but
once you notice it, it's gonna bug you.)
And there are legitimate, if less common, reasons to expect the more consistent
behaviour: checking for 'when True' as a catch-all near the end of a series of
'when's is not unreasonable. And what I said above about using "if" for Bools
because there are only two possibilities isn't quite true — since this is Perl,
there are at least four useful values a Bool can have: true, false, undef, or
Whatever. It wouldn't be unnatural to reach for 'when' if you need to
distinguish those.
I did like "whenever" because it seems to fit well psychologically. (Your
psyche may vary.) But now that I've started thinking of the two issues here
separately (i.e., breaking out of a block vs a shortcut for $_~~), I'm less
inclined to make a special case just for "when". It really would be useful to
have a $_~~-shortcut that worked with "if", or other places. My first thought
was separating the -ever somehow, so that you could say "when ever(3<4)" or "if
ever (3<4)"; but that means most ordinary 'if's would have to be written 'if
ever...' which is no good. (Except perhaps for Perl poetry.)
Using "if:" or "when:" with the colon to indicate "instead of evaluating this
conditional, ~~ it to $_" is concise, but restricted to 'if' or 'when'
statements. What if we had a metaoperator that could convert a normal op into
an implied-topic op, i.e. curry it assuming $_ as the first argument?
I don't have a good name, so for now I'll just call it "_", in reminiscence of
$_. So "$_ == $foo" would become "_== $foo", "$_ > bar" would be "_> bar",
etc. And just as Z can act as an operator by itself, so plain "_" could stand
for the default topic-op, ~~. Hence "$_ ~~ $X" would just be "_$X" (unless you
changed the default default topic-op to something else.)
I really like the idea of being able to take the topic-shortcut anywhere. I'm
less thrilled about the idea of having to say "when _ $blah" all over the
place.... But it is more consistent and more general. And it's making me
think of ".foo", which reminds me of another catch with the current spec: "when
.foo" means "when $_ ~~ $_.foo" — unless the .foo method happens to return a
Bool, of course — but it's more likely that it ought to be "when *.foo" or
"when ?.foo". So that's a pretty plausible case for catching people out.
-David