列表断链

List breaks the chain

在看 RaycatWhoDat 喜欢 Raku 的时候,我才发现,List of Matches 不是 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

第2次调用 replace-with 会失败,因为带 :g.match 会返回一个 Match 的列表。当然,我们可以玩个简单的游戏,直接使用 sub,当调用 :g 时,它就会做正确的事情。不过这不会是一篇好的博客文章。

为了让 replace-with 在 List 中工作,我们可以使用 where 从句。任何匹配都会有原始 Str 的副本,但不会有原始 Regex 的副本,所以我们实际上必须建立一个包含所有未匹配的 Str 的列表。这可以通过使用存储在 .from.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

如果原始字符串的结尾没有匹配,那么匹配列表就会少一个,无法被直接压缩进去。这也是为什么我必须将替换列表扩展一个空字符串再喂给 Z 的原因。

所以如果 subst 做的很好,为什么还要用 .replace-with 呢?因为有时我们不得不使用 $/

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

通常我们可以改变代码,但当一个模块的例程返回 Match 或其列表时,我们就没戏了。为了完整起见,我们还需要几个 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;
}

即使问题是可以解决的,它仍然困扰着我。我们在 Raku 的很多地方都有 :g 提供相当多的 DWIM。在一些地方,这个概念打破了,几乎所有的地方都与列表有关。往往是 ». 来救场。当我们真正需要在列表上工作而不是单个元素时,即使这样也不行。字符串上的方法只是工作,因为在这种情况下,我们刻意避免将字符列表拆开。

如果你关注这个博客,你就会知道我非常倾向于使用运算符。遗憾的是,., .?». 并不是我们可以重载的真正的中缀运算符。我们也不能声明以 . 开头的中缀运算符,或者我们可以引入一个操作符,将它的 LHS 变成一个列表,然后对能够处理特定类型列表的 multis 进行调度。

如果不这样做,我们需要改变 .match 来返回 List 的一个子类,这个子类得到了方法 .replace-with。我们可以把它插入到 Cool 中,但那里已经很拥挤了。

我们并没有一个很好的方法来增强内置方法的返回值。所以这将不得不在 CORE 中解决。

原文链接: https://gfldex.wordpress.com/2020/09/25/list-breaks-the-chain/

comments powered by Disqus