允许方法对我的类型的列表进行操作?

Allowing a method to operate on a List of my type?

Raku 通过在我的类型上实现 [multi?|sub?] 方法,可以很容易地在我的新类型上支持现有功能。但是,我想知道它是否还提供了一种方法,使现有(或新)方法适用于列表或我的类型的其他位置集合(不扩充列表,这是走向疯狂之路的第一步……)。

更具体地说,这就是我所说的,使用 Point class 这似乎是每个人都喜欢的例子:

class Point { 
    has $.x; has $.y; 
    method sum(Point $p) { Point.new: :x($!x + $p.x) :y($!y + $p.y) }
}

my $p1 = Point.new: :8x :3y;

my $p2 = Point.new: :1x :9y;

$p1.sum: $p2; # this works     # OUTPUT: «Point.new(x => 9, y => 12)»

($p1, $p2).sum;  # This is what I want to be able to do

(我知道,在 特定的 案例 .sum 中,有一个解决方案涉及在 [= 上实施 Numeric 强制方法11=],但我对更通用的解决方案感兴趣。)

有没有 good/clean 方法来做到这一点?还是我只是使用缩减和类似功能对列表中的点求和而不能在该列表上使用 .sum 方法?

您可以添加一个专用的 infix:<+> 候选人,然后您可以使用 [+] metaop:

multi sub infix:<+>(Point:D \a, Point:D \b) { 
    a.sum(b)
}

say [+] $p1,$p2;  # Point.new(x => 9, y => 12)

List.sum方法在内部使用infix:<+>,但遗憾的是它首先尝试调用Numeric。这可能被认为是一个错误:将对此进行调查。

为了回答您的问题,我发明了所谓的“4 步重新路由”。这有四个步骤:

  1. 想要控制的模式匹配复合数据结构;

  2. 将匹配的数据结构强制转换为新类型;

  3. 模式匹配在新类型上调用的后续例程;

  4. 根据需要重新路由匹配的例程。


我尝试过多种方法来实现这 4 步重新路由,一些使用 OO,另一些只是函数。以下是我为您的特定示例提出的解决方案的最短路径:

role Point {                                              # <-- Make `role`
    has $.x; has $.y; 
    multi method sum(Point $p) { Point.new: :x($!x + $p.x) :y($!y + $p.y) }

    multi method COERCE ($_) { $_ but Point }             # <-- Hook `sum`
    multi method sum { self.List[0].sum: self.List[1] }   # <-- Reroute `sum`
}

my $p1 = Point.new: :8x :3y;
my $p2 = Point.new: :1x :9y;

say Point($p1, $p2).sum; # OUTPUT: «Point.new(x => 9, y => 12)»

让我们回顾一下我开始的四个重新路由步骤以及我是如何在上面实现它们的:

  1. 想要控制的模式匹配复合数据结构;

    我在 say Point($p1, $p2).sum; 中插入了一个 Point(...) 调用。因为 Point 是触发 Raku 强制协议的类型。这是启动模式匹配过程的一种方式。协议的一部分是在某些情况下调用类型的 COERCE 方法。所以我在你的类型中添加了一个 multi method COERCE... 方法,它被调用了。在这个例子中,这个调用的实际“模式匹配”方面是最小的——只是 $_——但我将在下面这个解决方案的另一个版本中详细说明它。

  2. 将匹配的数据结构强制转换为新类型;

    这是 $_ but Point 代码。混合 Point 是最小但足够的强制。 (请注意,它可以反过来写——Point but $_,它仍然有效。)为了完成这项工作,我将 Point 的类型声明符从 class 切换为 role.

  3. 模式匹配在新类型上调用的后续例程;

    这是 multi method sum 声明。

  4. 根据需要重新路由匹配的例程。

    这是 self.List[0].sum: self.List[1] 代码。


作为步骤 1 中更具体的模式匹配的说明:

    multi method COERCE (List $_ (Point, Point)) { $_ but Point }

如果您要使用这 4 步重新路由,那么像上面这样的签名模式可能非常明智。


有助于第 4 步代码更简洁的习惯用法:

    multi method sum ($_: ;; $/ = .List) { [=12=].sum:  }

这看起来 非常丑陋。为了什么?我为什么把它包括在内?嗯,我希望你能看到它暗示的潜力。这真的与你的问题或我的回答无关。但是我不遵循关于何时分享某些东西的理性规则。我希望我能特别吸引你,@codesections,以及任何读懂这篇文章的人。

首先,事实上,大多数人没有意识到 Captures 是 Raku 中一项重要的未充分利用的人体工程学创新。 Capture 不仅仅用于捕获参数。它不仅仅是 Match 的父级 class。这是一种通用的“嵌套数据结构”类型。

其次,按照类似的思路,$/ 不仅仅是当前的 Match 变量。它也是 Capture 的一个非常方便的自动解构,也就是说对通用嵌套数据结构的自动解构。

我一直在考虑如何最好地将以下内容引入 Raku 文化中,但我认为如果具有与三个标点变量 [=18] 相对应的适当 elegant/practical/nuanced 特征会很棒=]、$/$!.

我将我的稻草人提案命名为(在我的脑海中)“这是数据,好吗?”。注意这是三个字。在过去的几年里,我在脑海中想出了很多关于这个的细节,但我希望你能凭直觉开始想象,如果我们沿着我建议的道路走下去,我们会走向何方。我真的应该把它写成一个要点;这个 SO 答案的奇怪附录是预付款。