Home > Raku > List breaks the chain

List breaks the chain

While watching RaycatWhoDat liking Raku, I realised that a List of Matches is not a MatchList.

say 'a1'.match(/ \d /).replace-with('#');
say 'a1b2'.match(/ \d /, :g).replace-with('#');
# OUTPUT: a#
#         No such method 'replace-with' for invocant of type 'List'
            in block <unit> at /home/dex/tmp/tmp-2.raku line 8

The 2nd call of replace-with will fail because .match with :g returns a List of Match. Of course we can play the easy game and just use subst which does the right thing when called with :g. This wouldn’t make a good blog post though.

To make replace-with work with a List we can use a where-clause. Any Match will have a copy of the original Str but not to the original Regex so we actually have to build a list of Str of everything that was not matched. This can be done by using the indices stored in .from and .to.

multi sub replace-with(\l where (.all ~~ Match), \r --> Str) {
    my $orig := l.head.orig;
    my @unmatched;

    @unmatched.push: $orig.substr(0, l.head.from);
    for ^(l.elems - 1) -> $idx {
        @unmatched.push: $orig.substr(l[$idx].to, l[$idx+1].from - l[$idx].to);
    }

    @unmatched.push: $orig.substr(l.tail.to);

    (@unmatched Z (|(r xx l.elems), |'')).flat.join;
}

say 'a1vvvv2dd3e'.match(/ \d /, :g).&replace-with('#');
# OUTPUT: a#vvvv#dd#e

If the original string does not end with a match, the list of matches will be one short to be just zipped in. That’s why I have to extend the list of replacements by an empty string before feeding it to Z.

So if subst is doing it right why bother with .replace-with? Because sometimes we have to use $/.

if 'a1bb2ccc3e' ~~ m:g/ \d / {
    say $/.&replace-with('#');
}

Often we could change the code but when a routine from a module returns Match or a list thereof, we are out of luck. For completeness we need a few more multies.

multi sub replace-with(Match \m, \r --> Str) {
    m.replace-with(r);
}

multi sub replace-with(Match \m, &r --> Str) {
    m.replace-with(r(m));
}

multi sub replace-with(\l where (.all ~~ Match), &r) {
    my $orig := l.head.orig;
    my @unmatched;

    @unmatched.push: $orig.substr(0, l.head.from);
    for ^(l.elems - 1) -> $idx {
        @unmatched.push: $orig.substr(l[$idx].to, l[$idx+1].from - l[$idx].to);
    }

    @unmatched.push: $orig.substr(l.tail.to);

    (@unmatched Z (|l.map(&r), |'')).flat.join;
}

Even if the problem is solvable it still bugs me. We have :g in many places in Raku to provide quite a lot of DWIM. In some places that concept breaks and almost all of them have to do with lists. Often ».comes to the rescue. When we actually have to work on the list and not the individual elements, even that doesn’t work. The methods on strings just work because in this case we deliberately avoid breaking the list of characters apart.

If you follow this blog you know that I’m leaning strongly towards operators. Sadly ., .? and ». are not real infixes which we can overload. Nor can we declare infixes that start with a . or we could introduce an operator that turns it’s LHS to a list and then does a dispatch to multis that can handle lists of a certain type.

Without that we need to change .match to return a subclass of List that got the method .replace-with. We could stick it into Cool but that is a crowded place already.

We don’t really have a nice way to augment the return values of builtin methods. So this will have to be fixed in CORE.

Categories: Raku
  1. No comments yet.
  1. September 28, 2020 at 18:46

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: