7

在 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

由于自定义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

那么如何获取在别处创建的散列,并将其转换为类的默认构造函数所需的命名参数类型呢?

4

1 回答 1

6

使用默认构造函数

默认构造函数将命名.new参数映射到公共属性。

在您的示例中,您将哈希作为位置参数传递。您可以使用|语法将散列条目作为命名参数插入到参数列表中:

$x = Local::Class.new(|%hash);

但是,请注意,如果您的类具有如下数组属性,这将导致问题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],])

这是因为像所有哈希一样,%hash将其每个值都放在一个项目容器中。因此该属性将被初始化为@.z = $([1, 2]),这将产生一个由单个元素组成的数组,该数组是原始数组。

避免这种情况的一种方法是使用 aCapture而不是 a 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])

或者使用 aHash但随后将其值解容器化<>并将整个事物转换为 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])

使用自定义构造函数

如果您希望在类本身而不是在使用该类的代码中处理这个问题,您可以根据自己的喜好修改构造函数。

请注意,默认构造函数.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])

将 array-in-an-item-container 绑定到@参数会自动删除项目容器,因此它不会受到上述“数组中的数组”问题的影响。

缺点是您必须在该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])
于 2016-12-01T01:00:59.530 回答