`%_` 并检测方法中不需要的命名参数
`%_` 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, ;
解释:
我用了两次%_
:
- 我用
x y z
从 F3
调用方法 w-sum
,所以
%_={x => 1, y => 2}
- 我通过
callsame
和 x y z
从 F
调用方法 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
.
那么 C1
的 m
方法中的签名是怎么说的。好吧,*%
部分说“我将获取一个绝对包含任何命名参数的列表”,然后 ()
部分添加“只要该列表中没有任何内容!” .
这让我们得到了您正在寻找的东西:一种拒绝意外命名参数的方法(并且 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 Hash
和 Hash isa Map
以及 Map isa Cool
和 Cool isa Mu
。所以这意味着 type-constrained 和 any of Stash
、Hash
、Map
、Cool
或Mu
将接受一个 Stash
– 由 Stash
来确保它 可以 有效地传递给任何这样的函数而不破坏任何东西。当然,在大多数情况下,Stash
通过继承来处理这个问题:您可以在 Stash
上调用的大多数方法实际上并未在 上 上定义Stash
– 它们是继承的,因此自动兼容 superclass 方法(因为它们 是 superclass 方法)。
但是,对于 Stash
选择覆盖的方法,Stash
的工作之一是确保它不会破坏任何超classes 预期行为。这同样适用于 Map
——但更是如此。如果有方法n Map
覆盖的 Cool
,Map
不仅需要考虑 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 的处理方式,有一些很好的理由可以考虑不这样做。
据我所知,如果在签名中找不到(不确定为什么!),方法的命名参数将转到 %_
。为了检测这一点,我
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, ;
解释:
我用了两次%_
:
- 我用
x y z
从F3
调用方法w-sum
,所以%_={x => 1, y => 2}
- 我通过
callsame
和x y z
从F
调用方法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
.
那么 C1
的 m
方法中的签名是怎么说的。好吧,*%
部分说“我将获取一个绝对包含任何命名参数的列表”,然后 ()
部分添加“只要该列表中没有任何内容!” .
这让我们得到了您正在寻找的东西:一种拒绝意外命名参数的方法(并且 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 Hash
和 Hash isa Map
以及 Map isa Cool
和 Cool isa Mu
。所以这意味着 type-constrained 和 any of Stash
、Hash
、Map
、Cool
或Mu
将接受一个 Stash
– 由 Stash
来确保它 可以 有效地传递给任何这样的函数而不破坏任何东西。当然,在大多数情况下,Stash
通过继承来处理这个问题:您可以在 Stash
上调用的大多数方法实际上并未在 上 上定义Stash
– 它们是继承的,因此自动兼容 superclass 方法(因为它们 是 superclass 方法)。
但是,对于 Stash
选择覆盖的方法,Stash
的工作之一是确保它不会破坏任何超classes 预期行为。这同样适用于 Map
——但更是如此。如果有方法n Map
覆盖的 Cool
,Map
不仅需要考虑 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 的处理方式,有一些很好的理由可以考虑不这样做。