In scala, pattern match
has guard pattern
:
val ch = 23
val sign = ch match {
case _: Int if 10 < ch => 65
case '+' => 1
case '-' => -1
case _ => 0
}
Is the Raku version like this?
my $ch = 23;
given $ch {
when Int and * > 10 { say 65}
when '+' { say 1 }
when '-' { say -1 }
default { say 0 }
}
Is this right?
Update: as jjmerelo suggested, i post my result as follows, the signature version is also interesting.
multi washing_machine(Int \x where * > 10 ) { 65 }
multi washing_machine(Str \x where '+' ) { 1 }
multi washing_machine(Str \x where '-' ) { -1 }
multi washing_machine(\x) { 0 }
say washing_machine(12); # 65
say washing_machine(-12); # 0
say washing_machine('+'); # 1
say washing_machine('-'); # -1
say washing_machine('12'); # 0
say washing_machine('洗衣机'); # 0
TL;DR You've encountered what I'd call a WTF?!?: when Type and ...
fails to check the and
clause. This answer talks about what's wrong with the when
and how to fix it. I've written another answer that focuses on using where
with a signature.
If you want to stick with when
, I suggest this:
when (condition when Type) { ... } # General form
when (* > 10 when Int) { ... } # For your specific example
This is (imo) unsatisfactory, but it does first check the Type
as a guard, and then the condition if the guard passes, and works as expected.
No.
given $ch {
when Int and * > 10 { say 65}
}
This code says 65
for any given integer, not just one over 10
!
WTF?!? Imo we should mention this on Raku's trap page.
We should also consider filing an issue to make Rakudo warn or fail to compile if a when
construct starts with a compile-time constant value that's a type object, and continues with and
(or &&
, andthen
, etc), which . It could either fail at compile-time or display a warning.
Here's the best option I've been able to come up with:
when (* > 10 when Int) { say 65 }
This takes advantage of the statement modifier (aka postfix) form of when
inside the parens. The Int
is checked before the * > 10
.
This was inspired by Brad++'s new answer which looks nice if you're writing multiple conditions against a single guard clause.
I think my variant is nicer than the other options I've come up with in previous versions of this answer, but still unsatisfactory inasmuch as I don't like the Int
coming after the condition.
Ultimately, especially if/when RakuAST lands, I think we will experiment with new pattern matching forms. Hopefully we'll come up with something nice that provides a nice elimination of this wart.
We can begin to see the underlying problem with this code:
.say for ('TrueA' and 'TrueB'),
('TrueB' and 'TrueA'),
(Int and 42),
(42 and Int)
displays:
TrueB
TrueA
(Int)
(Int)
The and
construct boolean evaluates its left hand argument. If that evaluates to False
, it returns it, otherwise it returns its right hand argument.
In the first line, 'TrueA'
boolean evaluates to True
so the first line returns the right hand argument 'TrueB'
.
In the second line 'TrueB'
evaluates to True
so the and
returns its right hand argument, in this case 'TrueA'
.
But what happens in the third line? Well, Int
is a type object. Type objects boolean evaluate to False
! So the and
duly returns its left hand argument which is Int
(which the .say
then displays as (Int)
).
This is the root of the problem.
(To continue to the bitter end, the compiler evaluates the expression Int and * > 10
; immediately returns the left hand side argument to and
which is Int
; then successfully matches that Int
against whatever integer is given
-- completely ignoring the code that looks like a guard clause (the and ...
bit).)
If you were using such an expression as the condition of, say, an if
statement, the Int
would boolean evaluate to False
and you'd get a false negative. Here you're using a when
which uses .ACCEPTS which leads to a false positive (it is an integer but it's any integer, disregarding the supposed guard clause). This problem quite plausibly belongs on the traps page.
From what I see in this answer, that's not really an implementation of a guard pattern in the same sense Haskell has them. However, Perl 6 does have guards in the same sense Scala has: using default patterns combined with ifs.
The Haskell to Perl 6 guide does have a section on guards. It hints at the use of where
as guards; so that might answer your question.
Years ago I wrote a comment mentioning that you had to be more explicit about matching against $_
like this:
my $ch = 23;
given $ch {
when $_ ~~ Int and $_ > 10 { say 65}
when '+' { say 1 }
when '-' { say -1 }
default { say 0 }
}
After coming back to this question, I realized there was another way.when
can safely be inside of another when
construct.
my $ch = 23;
given $ch {
when Int:D {
when $_ > 10 { say 65}
proceed
}
when '+' { say 1 }
when '-' { say -1 }
default { say 0 }
}
Note that the inner when
will succeed
out of the outer one, which will succeed
out of the given
block.
If the inner when
doesn't match we want to proceed on to the outer when
checks and default
, so we call proceed
.
This means that we can also group multiple when
statements inside of the Int
case, saving having to do repeated type checks. It also means that those inner when
checks don't happen at all if we aren't testing an Int
value.
when Int:D {
when $_ < 10 { say 5 }
when 10 { say 10}
when $_ > 10 { say 65}
}
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