Perl 6:如何检查 `new` 的无效参数?

Perl 6: how to check `new` for invalid arguments?

检查是否将无效参数传递给构造方法的最简单方法是什么new

use v6;
unit class Abc;

has Int $.a;

my $new = Abc.new( :b(4) );

TLDR;如果您只是担心有人不小心将 :a(4) 键入为 :b(4),根据需要标记$.a可能会更好。

class ABC {
  has Int $.a is required;
}

ABC.new( :b(4) ); # error
# The attribute '$!a' is required, but you did not provide a value for it.

一个快速的 hack 是添加一个子方法 TWEAK 以确保您没有指定的任何命名值都不存在。它不会干扰 newBUILD 的正常工作,因此正常类型检查无需重新实现它们即可工作。

class ABC {
  has Int $.a;

  submethod TWEAK (

    :a($), # capture :a so the next won't capture it

    *%     # capture remaining named
      ()   # make sure it is empty

  ) {}
}

一种稍微复杂一点(但仍然很老套)的方法,应该继续为子类工作,并且不需要通过添加更多属性来更新:

class ABC {
  has Int $.a;

  submethod TWEAK (

    *%_    # capture all named

  ) {

    # get the attributes that are known about
    # (should remove any private attributes from this list)
    my \accepted = say self.^attributes».name».subst(/.'!'/,'');

    # ignore any that we know about
    %_{|accepted}:delete;

    # fail if there are any left
    fail "invalid attributes %_.keys.List()" if %_

  }
}

ClassX::StrictConstructor module 应该有所帮助。使用 zef install ClassX::StrictConstructor 安装它并像这样使用它:

use ClassX::StrictConstructor;

class Stricter does ClassX::StrictConstructor {
    has $.thing;
}

throws-like { Stricter.new(thing => 1, bad => 99) }, X::UnknownAttribute;

编写自定义new

将此方法声明添加到您的 class:

method new ( :$a is required ) { callsame }

:$a 绑定到名为 anamed argument(即键为 'a' 的 key/value 对,例如 a => 4 ).

参数名称后面的 is required 使 a 参数成为 mandatory.

现在不传递名为 a 的命名参数的调用将被拒绝:

Abc.new( :b(4) ) ;       # Error: Required named parameter 'a' not passed

new 方法的主体调用 callsame. It calls the new that your class inherits, namely Mu's new。此例程遍历您的 class 及其祖先初始化名称对应于命名参数的属性:

Abc.new( :a(4) ) ;       # OK. Initializes new object's `$!a` to `4`
Abc.new( :a(4) ).a.say ; # Displays `4`

UPD: 请参阅 了解更简单的方法,该方法仅将 is required 直接添加到 现有属性声明class:

has Int $.a is required; # now there's no need for a custom `new`

ABC.new( :b(4) ); # The attribute '$!a' is required...

请注意错误消息中从 Required named parameter 'a' not passedattribute '$!a' is required... 的转变。这反映了从添加自定义 new 到随后自动绑定到属性的必需例程 参数 到仅将 is required 直接添加到 属性.

如果这还不够,请继续阅读。

默认new

  • 接受 任何和所有命名参数(对)。然后它将它们传递给在对象构造期间自动调用的其他例程。您在示例代码中传递了命名参数 :b(4)。已被采纳并转发。

  • 拒绝任何和所有位置参数(不是对)。

你原代码中的new调用被接受是因为你只传递了一个命名参数,所以没有位置参数,从而满足了默认直接完成的参数验证new

拒绝未知的命名参数(以及任何位置参数)

method new ( :$a!, *%rest ) { %rest and die 'nope'; callsame }

*%rest 将除名为 a 的参数之外的所有命名参数“吞噬”到 slurpy hash 中。如果它不为空,则 die 触发。

另见

需要位置参数而不是命名参数

使用namedparameters/arguments来自动初始化相应的对象属性几乎总是更简单和更好,如上所示。如果你想让人们很容易从你的 class 继承并添加他们也希望在 new 期间初始化的属性,这一点尤其正确。

但是如果你想推翻这个经验法则,你可以要求一个或多个 positional parameters/arguments 并调用新对象上的方法来从传递的参数初始化它。

也许最简单的方法是更改​​属性,使其公开可写:

has Int $.a is rw;

并添加如下内容:

method new ( $a ) { with callwith() { .a = $a; $_ } }

callwith() 例程调用继承的 new,不带参数、位置参数或命名参数。默认(Mu)newreturns一个新构造的对象。 .a = $a 设置它的 a 属性和 $_ returns 它。所以:

my $new = Abc.new( 4 );
say $new.a ; # Displays `4`

如果您不想要公开可写的属性,请保持 has 不变,而是编写如下内容:

method !a  ( $a ) { $!a = $a } # Methods starting `!` are private
method new ( $a ) { with callwith() { $_!a($a); $_ } }