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.
-
September 28, 2020 at 18:462020.39 The Releaser – Rakudo Weekly News