`%_` 并检测方法中不需要的命名参数

`%_` and detecting unwanted named arguments to a method

据我所知,如果在签名中找不到(不确定为什么!),方法的命名参数将转到 %_。为了检测这一点,我

die "Extra args passed" if %_;

因为我记得做的和值得做的方法。有没有办法用一些装饰器之类的东西来自动化这个?或者我想念 %_ 在面向对象的 Raku 编程中实际上是一个非常有用的东西?

Is there a way to automate this for example with some decorator kind of thing?

我目前不知道这样做的方法。

我曾经开发了一个方法特征来从方法的签名中删除隐式的 *%_。希望我可以简化对采用许多不同(组合)命名参数的多种方法的调度。

结局并不好。我不记得确切的原因了,但我决定推迟尝试这样做,直到 RakuAST 分支登陆之后。

Or I miss and %_ is actually a very useful thing in object oriented Raku programming?

我认为,它对以下内容很有用:

class F {
    has $.x;
    has $.y;
    multi method w-sum (:$x,:$y) { $x*$!x + $y*$!y };
}

class F3 is F {
        has $.z;
        method w-sum (:$z) { $z*$!z + callsame }
        # vs method w-sum (:$x, :$y, :$z) { $z*$!z + callwith( :$x, :$y ) }
}

my F3 $point3D .= new: :2x, :3y, :4z, ;

$point3D.w-sum: :1x, :2y, :3z, ;

解释:

我用了两次%_

  1. 我用 x y zF3 调用方法 w-sum,所以 %_={x => 1, y => 2}
  2. 我通过 callsamex y zF 调用方法 w-sum,所以 %_={z => 3}

尝试调试版本:

class F {
    has $.x;
    has $.y;
    multi method w-sum(:$x,:$y) { say 'from F: ', %_; $x*$!x + $y*$!y }
}

class F3 is F {
        has $.z;
        method w-sum (:$z) { say 'from F3: ', %_; $z*$!z + callsame }
        # vs method w-sum (:$x, :$y, :$z) { $z*$!z + callwith( :$x, :$y) }
}

my F3 $point3D .= new: :2x, :3y, :4z, ;

say $point3D.w-sum: :1x, :2y, :3z, ;
from F3: {x => 1, y => 2}
from F: {z => 3}
20

Is there a way to automate this for example with some decorator kind of thing?

因为在 Raku 中总是有不止一种方法,所以实际上有 多种 方法可以相当容易地防止自动 *%_ 方法参数。我将向您展示两个(并解释它们的工作原理),然后尝试说服您,事实上“%_ 实际上是面向对象的 Raku 编程中非常有用的东西”。

答案:拒绝意外的命名参数(*%_

考虑这段代码:

class C0           { method m        { say 'm called' } }
class C1           { method m(*% ()) { say 'm called' } }
class C2 is hidden { method m        { say 'm called' } }

my $c = C0.new;
$c.m(:unexpected); # OUTPUT: «m called»

C0 是一个标准的 class,正如您已经注意到的,它很乐意接受意外的命名参数。但是如果我们用 C1 尝试同样的事情,我们会得到这个错误:

Unexpected named argument 'unexpected' passed in sub-signature

而且,如果我们用 C2 试一下,我们会得到这个非常相似的:

Unexpected named argument 'unexpected' passed

我们当前目的的最大区别是 C1 只是不允许 m 的意外命名参数 – 我们可以创建其他 C1 方法 do 接受意外的命名参数。相反,使用 C2,我们阻止 classes 方法中的 any 接受意外的命名参数(并做更多,但很快就会做更多)。

这些是如何工作的?

C1

C1 通过使用 named slurpy parameter and then destructuring that parameter into a sub-signature 防止 m 接受任何意外参数——在本例中,sub-signature 恰好由一个空列表组成。

稍微分解一下,考虑以下代码:

sub f(:$group-name!, *@ids (Int, Int?, Int?, Int?) {}

那个签名是说“我必须有一个 :group-name 参数和一些@ids。那些@ids 必须至少有一个 Int,最多可以有四个 Int——但没有超过四个,并且没有 non-Ints)”。所以我们可以用:group-name<foo>, 1调用f。或者用 :group-name<foo>, 1, 42, 55。但是如果我们尝试用 :group-name<foo>, 1, 42, 55, 9, 9, 9 调用它,我们会得到错误:Too many positionals passed to 'f'; expected 1 to 4 arguments but got 6 in sub-signature of parameter @ids.

那么 C1m 方法中的签名是怎么说的。好吧,*% 部分说“我将获取一个绝对包含任何命名参数的列表”,然后 () 部分添加“只要该列表中没有任何内容!” .

这让我们得到了您正在寻找的东西:一种拒绝意外命名参数的方法(并且 expected 命名参数根本不会造成任何问题 – 你会在 *% 之前声明那些,它们会在到达 sub-signature.

之前被处理

C2

C2 既简单又有点矫枉过正。 C2 使用 hidden trait which stops C2 from participating in the redistpatch 过程——并且,作为副作用,阻止 C2 的方法被赋予自动 *%_ 参数。

解释 Raku 的(重新)调度过程的全部范围超出了这个答案的范围,但我只想说,这取决于你想要什么,这可能正是你正在寻找的,或者它可能成为不必要的后果。

但是你应该(也许)忽略那些答案

让我们回到你是如何结束你的问题的。你问你是否错过了使 *%_ “实际上是面向对象的 Raku 编程中非常有用的东西”的东西。我认为,是的,*%_ 的自动添加实际上非常有用。

有很多不同的方法可以解释为什么会这样,但是,既然你提到了 object-oriented Raku,我将从那个角度来看待它(我个人写得更多 functional-programming Raku,但两者都有一席之地)。

因此,从 OOP 的角度来看,添加 *%_ 使得 更容易编写遵循 Liskov_substitution_principle (aka, the L in SOLID OOP).[=88 的 Raku 方法=]

我不确定你对这个原则有多熟悉,所以我就它简单说几句(如果我说的很明显,我深表歉意)。 LSP 说“程序中的对象应该可以用其子类型的实例替换,而不会改变该程序的正确性”。

这可能有点抽象,所以这里有一个 Raku 示例……好吧,仍然有点抽象,但请耐心等待。

所以,考虑 Raku Stash class。它现在做什么并不重要;我提出它是因为它的类型图:

所以 Stash isa HashHash isa Map 以及 Map isa CoolCool isa Mu。所以这意味着 type-constrained 和 any of StashHashMapCoolMu 将接受一个 Stash – 由 Stash 来确保它 可以 有效地传递给任何这样的函数而不破坏任何东西。当然,在大多数情况下,Stash 通过继承来处理这个问题:您可以在 Stash 上调用的大多数方法实际上并未在 上定义Stash – 它们是继承的,因此自动兼容 superclass 方法(因为它们 superclass 方法)。

但是,对于 Stash 选择覆盖的方法,Stash 的工作之一是确保它不会破坏任何超classes 预期行为。这同样适用于 Map——但更是如此。如果有方法n Map 覆盖的 CoolMap 不仅需要考虑 Map 的调用者,还需要考虑 Stash 的调用者:毕竟,Stash 可能 不会 覆盖该方法。因此 Map 需要确保它可以处理 Cool wants/needs 需要处理的任何事情——从理论上讲,这是随时可能发生的事情。 (在实践中,当然,Cool 非常稳定,它和 Map 都是 Rakudo 的一部分。但是同样的事情也适用于第 3 方 classes ,其中 API 变化更快,您可能没有能力协调变化。)

特别是,“处理 Cool 可以处理的任何事情”意味着能够使用 Cool 的参数调用者调用 而不会 抛出类型错误– 即使这些论点可能会改变。

这就是 *%_ 的用武之地。除非其中之一选择退出,否则 method chain 的每个成员都接受任何命名参数。因此,Cool 可以自由添加新的命名参数,Map/Stash 的调用者(毕竟,他们不应该关心或 需要关心 他们调用的对象是否可以传入相应的命名参数。而且,即使这些方法调用由从未听说过新参数的方法解决,也绝对不会中断。

当然,如果它只是被*%_吃掉,新参数实际上不会任何事情,这(取决于class/method) 可能不理想。但这正是 re-dispatching 回归并让干预 class 作用于方法 仍然将其传递给超级 class 的地方。但是,这又超出了范围。


因此,在对一个简短的问题进行长回答后,底线是您可以 阻止方法接受意外的命名参数。但是,根据您对 programming/Raku 的处理方式,有一些很好的理由可以考虑不这样做。