string matching in `MAIN()` parameters
The biggest problem is you used both *
to make a WhateverCode lambda and m
.
m/…/
gets matched against whatever happens to be in $_
.
This effectively happens outside of the ~~
.
If you had only used m/…/
it would have worked
sub MAIN(
Str :$r where m:i/< aaa bbb ccc >/ = "bbb"
) { say $r }
You could have also put the :i
inside of the regex
/:i < aaa bbb ccc >/
A where
clause does smartmatching, just like ~~
does smartmatching. So using both like you did is redundant.
In a smartmatch based feature the expression gets run with $_
is set to the value being matched. The result of that expression is then matched against the input.
I'm going to use a subset
to try and help explain it better
subset Foo of Str where * ~~ m:i/< aaa bbb ccc >/;
When you are match against it the first thing that happens is the Str
check.
(This is fairly performant, and the type specializer may be able to eliminate this check)
'ccc' ~~ Str; # Str.ACCEPTS('ccc');
Then the expression in the where
clause gets run with the value being checked in $_
.
my $result = do given 'ccc' { * ~~ m:i/< aaa bbb ccc >/ }
# $result holds a closure
What happens next is that the result gets smartmatched against the value being tested.
'ccc' ~~ $result; # $result.ACCEPTS('ccc');
That last one in this case will depend on whatever happened to be in $_
at the time.
Since that will now happen deep inside of the language, you might not have any control over $_
.
In the case of a where
clause it can be anything.
$_ = 'fubar';
# v--v
subset Foo where * ~~ m:i/
{ say '$_ = ', $_ }
{ say 'orig = ', $/.orig } # the string that is matched against
< aaa bbb ccc >
/;
my $result = 'ccc' ~~ Foo;
# $_ = fubar
# orig = fubar
say $result;
# False
Since 'fubar'
doesn't match /< aaa bbb ccc >/
the result is False
.
By adding * ~~
you also added spooky-action-at-a-distance.
It works without * ~~
, because Regex.ACCEPTS()
doesn't rely on $_
.
$_ = 'fubar';
subset Foo where m:i/
{ say '$_ = ', $_ }
{ say 'orig = ', $/.orig }
< aaa bbb ccc >
/;
my $result = 'ccc' ~~ Foo;
# $_ = fubar
# orig = ccc
say $result
# True
There is a reason Perl 6 does two levels of code execution is for code like the following
subset Bar where $_ eq any < aaa bbb ccc >;
my $result = do given 'ccc' { $_ eq any < aaa bbb ccc > }
# $result = True;
# 'ccc' ~~ $result;
$result.ACCEPTS('ccc');
# $result is True, and True.ACCEPTS() always returns True
Note that it can be shortened to:
subset Bar where any < aaa bbb ccc >;
my $result = do given 'ccc' { any < aaa bbb ccc > }
# $result = any < aaa bbb ccc >;
# 'ccc' ~~ $result;
$result.ACCEPTS('ccc');
# any(< aaa bbb ccc >).ACCEPTS('ccc')
This double code execution happens for all smartmatching features.
~~
'ccc' ~~ $_ eq any < aaa bbb ccc > # True.ACCEPTS('ccc') 'ccc' ~~ any < aaa bbb ccc > # any(…).ACCEPTS('ccc')
where
subset Baz where $_ eq any < aaa bbb ccc > subset Baz where any < aaa bbb ccc >
when
when $_ eq any < aaa bbb ccc > {…} when any < aaa bbb ccc > {…}
Basically this is so that you can smartmatch against a value or against an expression or against code.
(Code is really a type of value in Perl 6)
10 ~~ 0..10; # match against a value
10 ~~ Range.new(0,10); # same as previous line
10 ~~ 0 ≤ * ≤ 10; # match against code
10 ~~ -> $_ { 0 ≤ $_ ≤ 10 } # basically the same as previous line
10 ~~ 0 ≤ $_ ≤ 10; # match against an expression with $_
# (not the same a previous two lines)
I want to point out that regular expressions in Perl 6 are a type of function.
my &foo = sub ($_) {$_ eq 'abc'};
my &bar = * eq 'abc';
my &baz = /^ abc $/;
my &zzz = 'abc' # ERROR
So * ~~ /…/
is creating a function from something that is already a function.
It is also turning what would be double code execution into quadruple code execution.
In m/…/
the m
effectively causes the regex / function to run against whatever happens to be in $_
.
# $_ = Any; # initial value in $_
my &code = * ~~ m/abc/;
my &code = * ~~ ($_ ~~ /abc/); # same as previous line
There is also rx
, which is similar to m
except it always returns the regex itself rather than the result of calling it. (A bare /…/
acts like rx/…/
)
Smartmatching can be confusing when you are first starting out.
I'd argue it can be confusing to people who are otherwise experts in Perl 6.
(It's still a little confusing to me, and I know how it works.)
I also did a poor job trying to explain it here, but I was trying to be relevant to your question, and your use of ~~
made it harder to explain.
To keep myself sane I try to follow a few basic rules.
These apply to ~~
, where
, and when
.
Use a literal, or literal-like if possible.
… ~~ 42 … ~~ 'a' … ~~ any < aaa bbb ccc > … ~~ 1..10 # not actually a literal, but literal-like
If you are using an expression make sure it can only return
True
orFalse
.
The value being matched against is in$_
which can be helpful in thewhere
clause of asubset
.… ~~ 0 < $_ … ~~ $_.lc.contains('abc'); # Returns True or False when $_.lc.contains('abc') {…} … where $_.lc.contains('abc'); … ~~ $_.chars.Bool … ~~ ?$_.chars # prefix:« ? » coerces to Bool … ~~ ?~$_ # coerce to Str, coerce to Bool # True if the Str isn't empty # (so has the same effect as previous two examples)
If I had just used
$_.chars
it would only match if the value was numerically the same as the length.'1' ~~ $_.chars; # True '3.0' ~~ $_.chars; # True '1.0' ~~ $_.chars; # False (1.0 == 3) # that previous one is the same as do given '1.0' { $_.chars }.ACCEPTS( '1.0' ) # False # 3.ACCEPTS('1.0')
This is why I recommend making sure it returns a Bool.
There is an exception to this rule. Namely calling a routine that returns a value that you want to smart-match against.
… ~~ Date.today.day-of-week;
(This is a bad example, but is illustrative of what I mean.)
Use a Callable.
This effectively removes the first (expression) layer of code execution.
(The resulting value is whatever the result of the function is.)… ~~ *.lc.contains('abc') … ~~ {.lc.contains('abc')} … ~~ /:i abc/ # remember that a regex is a function when {.lc.contains('abc')} {…} … where {.lc.contains('abc')}; sub foo ( $_ ) { .lc.contains('abc') } … ~~ &foo when &foo {…} … where &foo;
Don't use
~~
in either of the other two smartmatching features.when * ~~ /…/ {…} # don't do this … where * ~~ /…/ # don't do this either … where $_ ~~ /…/ # No, … just no.
I would be a little more lenient of this one if there is a long expression, and this is only part of it.
when (.chars = 3 ?? $_ ~~ Str !! $_ ~~ Int) {…}
I have never come across any real code where this would have been useful.
Everytime I have seen
~~
used in a smartmatch, it would have worked better without it.
By sticking to the above rules m:i/…/
would still work, but for a different reason.
'ccc' ~~ m:i/ < aaa bbb ccc > /;
my $result = do given 'ccc' { m:i/ < aaa bbb ccc > / }
say $result.perl;
# Match.new(pos => 3, orig => "ccc", hash => Map.new(()), from => 0, list => (), made => Any)
$result = $result.ACCEPTS('ccc');
say $result.perl;
# Match.new(pos => 3, orig => "ccc", hash => Map.new(()), from => 0, list => (), made => Any)
.ACCEPTS()
on a instance of Match always returns itself. It is also always Trueish.
It still works correctly if there is a failure to match. (It returns something that is Falseish.)
Again a where
constraint, and a when
condition are the same as the right side of ~~
.
try this:
sub MAIN(
Str :$r where * ~~ / 'aaa' | 'bbb' | 'ccc' / = "bbb",
) { say $r }
< aaa bbb ccc >
in regex is not interpolated as array, should use as this:
my @a = < aaa bbb ccc >;
say so "aaa" ~~ /@a/;