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

一番下の 2 つの呼び出しは、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])、元の配列である単一要素の配列になります。

これを回避する 1 つの方法は、 a のCapture代わりに 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])

または、 a を使用してからHash、その値を でコンテナ化<>解除し、すべてを に変換して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 に答える