jja*_*jja 6 perl6 object-construction
在Perl5中,您可以执行以下操作:
#!/usr/bin/env perl
use 5.010;
package Local::Class {
use Moo;
has [qw( x y )] => ( is => 'ro');
sub BUILDARGS { shift; return (@_) ? (@_ > 1) ? { @_ } : shift : {} }
}
use Local::Class;
# Create object directly
my $x = Local::Class->new( x => 1, y => 10 );
say $x->x, ' ', $x->y; # 1 10
# Arguments from a hash
my %hash = ( x => 5, y => 20 );
$x = Local::Class->new(%hash);
say $x->x, ' ', $x->y; # 5 20
# Arguments from a hash reference
$x = Local::Class->new(\%hash);
say $x->x, ' ', $x->y; # 5 20
Run Code Online (Sandbox Code Playgroud)
底部的两个调用工作原理相同,因为自定义BUILDARGS方法基本上将它们都转换为Moo(se)预期的哈希引用类型.
但是我怎么能在Perl6中做同样的事情呢?
#!/usr/bin/env perl6
class Local::Class {
has $.x;
has $.y;
}
my $x;
# This works
$x = Local::Class.new( x => 1, y => 10 );
say $x.x, ' ', $x.y; # 1 10
# This doesn't
my %hash = %( x => 5, y => 20 );
$x = Local::Class.new(%hash);
# This doesn't either
$x = Local::Class.new(item(%hash));
# Both die with:
# Default constructor for 'Local::Class' only takes named arguments
Run Code Online (Sandbox Code Playgroud)
那么我如何获取其他地方创建的哈希值,并将其转换为类的默认构造函数所需的命名参数?
默认.new构造函数将命名参数映射到公共属性.
在您的示例中,您将散列作为位置参数传递.您可以使用|语法将哈希条目插入到参数列表中作为命名参数:
$x = Local::Class.new(|%hash);
Run Code Online (Sandbox Code Playgroud)
但请注意,如果您的类具有如下数组属性,则会导致问题has @.z:
class Local::Class {
has $.x;
has $.y;
has @.z;
}
my %hash = x => 5, y => 20, z => [1, 2];
my $x = Local::Class.new(|%hash);
say $x; # Local::Class.new(x => 5, y => 20, z => [[1, 2],])
Run Code Online (Sandbox Code Playgroud)
这是因为像所有哈希一样,%hash将每个值放在一个项容器中.因此该属性将被初始化为@.z = $([1, 2]),这将导致单个元素的数组,即原始数组.
避免这种情况的一种方法是使用a Capture而不是Hash:
my $capture = \( x => 5, y => 20, z => [1, 2] );
my $x = Local::Class.new(|$capture);
say $x; # Local::Class.new(x => 5, y => 20, z => [1, 2])
Run Code Online (Sandbox Code Playgroud)
或者使用a Hash然后对其值进行去容器化,<>并将整个事物转换为a Map(与a不同Hash,不会添加项容器),然后将其插入到参数列表中:
my %hash = x => 5, y => 20, z => [1, 2];
my $x = Local::Class.new(|Map.new: (.key => .value<> for %hash));
say $x; # Local::Class.new(x => 5, y => 20, z => [1, 2])
Run Code Online (Sandbox Code Playgroud)
如果你想在类本身而不是在使用该类的代码中处理这个问题,你可以根据自己的喜好修改构造函数.
请注意,默认构造函数.new调用.bless实际分配对象,而对象又调用.BUILD以处理属性的初始化.
所以最简单的方法是保持默认实现.new,但提供自定义.BUILD.您可以直接在其签名中将命名参数映射到属性,因此BUILD例程的主体实际上可以保持为空:
class Local::Class {
has $.x;
has $.y;
has @.z;
submethod BUILD (:$!x, :$!y, :@!z) { }
}
my %hash = x => 5, y => 20, z => [1, 2];
my $x = Local::Class.new(|%hash);
say $x; # Local::Class.new(x => 5, y => 20, z => [1, 2])
Run Code Online (Sandbox Code Playgroud)
将一个数组中的数组容器绑定到一个@参数会自动删除该项容器,因此它不会受到上述"数组中的数组"问题的影响.
缺点是您必须在该BUILD参数列表中列出您的类的所有公共属性.此外,您仍然必须|在使用该类的代码中插入哈希值.
要解决这两个限制,您可以实现这样的自定义.new:
class Local::Class {
has $.x;
has $.y;
has @.z;
method new (%attr) {
self.bless: |Map.new: (.key => .value<> for %attr)
}
}
my %hash = x => 5, y => 20, z => [1, 2];
my $x = Local::Class.new(%hash);
say $x; # Local::Class.new(x => 5, y => 20, z => [1, 2])
Run Code Online (Sandbox Code Playgroud)