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 or False.
    The value being matched against is in $_ which can be helpful in the where clause of a subset.

    … ~~ 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/;

Tags:

Raku