【问题标题】:Allowing a method to operate on a List of my type?允许一种方法对我的类型的列表进行操作?
【发布时间】:2021-06-29 16:48:45
【问题描述】:

Raku 通过在我的类型上实现 [multi?|sub?] 方法,可以非常轻松地支持我的新类型上的现有功能。但是,我想知道它是否还提供了一种将现有(或新)方法应用于列表或我类型的其他位置集合的方法(不增加列表,这是通往疯狂之路的第一步……)。

更具体地说,这就是我所说的,使用 Point 类,这似乎是每个人的首选示例:

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特定案例中,会有一个解决方案涉及在Point 上实施Numeric 强制方法,但我对更通用的解决方案。)

有没有好的/干净的方法来做到这一点?还是我只是使用归约和类似功能来对列表中的点求和,而无法在该列表上使用 .sum 方法?

【问题讨论】:

    标签: list methods types raku rakudo


    【解决方案1】:

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

    multi sub infix:<+>(Point:D \a, Point:D \b) { 
        a.sum(b)
    }
    
    say [+] $p1,$p2;  # Point.new(x => 9, y => 12)
    

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

    【讨论】:

    • CORE.setting 中的sum 方法在词法范围内没有Point 多调度候选,所以它没有生效。这是 [+] 调用 .sum 作为特例的一个相当不幸的副作用。
    • 但奇怪的是,[+] $p1, $p2 确实 工作,即使它确实在内部调用 .sum
    • 谢谢,知道这一点很有用——并且非常符合我的想法,即仅使用归约将是能够调用方法的一个很好的次优选择列表本身。
    • @ElizabethMattijsen [+] 仅在 infix:&lt;+&gt; 未更改的情况下在内部调用 .sum。如果您添加一个多候选人,它不会调用.sum,而是会以缓慢的方式进行。
    • 啊,是的,猜猜是谁把优化放在... :-)
    【解决方案2】:

    针对您的问题,我发明了我称之为“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) { $0.sum: $1 }
    

    这看起来非常难看。为了什么?为什么我把它包括在内?好吧,我希望你能看到它暗示的潜力。这真的与你的问题或我的回答无关。但我不遵循关于何时分享某事的理性规则。我希望我能特别吸引你,@codesections,以及其他任何能读懂我思想的人。

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

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

    我一直在考虑如何最好地将以下内容引入 Raku 文化,但我认为拥有与三个标点符号变量$_、@ 对应的适当优雅/实用/细微差别的功能会很棒987654345@和$!

    我已将我的稻草人提案(在我的脑海中)命名为“它是数据,好吗?”。注意它是三个字。在过去的几年里,我已经在脑海中想出了很多关于这个的细节,但我希望你能直观地开始想象如果我们沿着我建议的道路前进,我们可以去哪里。我真的应该把它写下来;这个 SO 答案的奇怪附录是首付。

    【讨论】:

    • 这是一种有趣的方法,而且我没有想到 - 尽管我知道 , 创建了一个列表,但我仍然需要查看该代码一分钟前,我意识到它说的是与Point.COERCE(List(1, 2)) 相同的东西,而不是用两个参数调用 &Point。另一方面,编写的代码实际上并没有为我运行。至少在 Rakudo 2021.03 上,运行此代码时出现none of these signatures match 错误。
    • 我累了,还有很多话要说,但想发布我所拥有的。然后漏了一条线!我已经解决了这个问题并写了一个更像我想要的答案。现在还有一个额外的附录“它是数据,好吗?”...
    猜你喜欢
    • 2015-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-15
    • 2013-07-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多