В Moose, как установить несколько значений по умолчанию с помощью одного вызова метода?
У меня есть два атрибута объекта, которые требуют дорогостоящих вычислений, поэтому я бы хотел, чтобы они были ленивыми. Они наиболее эффективно вычисляются вместе, поэтому я хотел бы рассчитать их одновременно. Предоставляет ли Moose способ сделать это?
Что бы я хотел, это нечто вроде "default" или "builder", но вместо того, чтобы возвращать значение по умолчанию, оно напрямую устанавливает атрибуты. Возвращаемое значение будет проигнорировано.
has max_things =>
is => 'rw',
isa => 'Int',
lazy => 1,
xxxxx => '_set_maxes';
has max_pairs =>
is => 'rw',
isa => 'Int',
lazy => 1,
xxxxx => '_set_maxes';
# Let just assume this is an expensive calculation or the max_*
# attributes are used rarely and a lot of objects are created.
sub _set_maxes {
my $self = shift;
if( $self->is_32_bit ) {
$self->max_things(2**31);
$self->max_pairs(12345 * 2);
}
else {
$self->max_thing(2**63);
$self->max_pairs(23456 * 2);
}
return;
}
ПРИМЕЧАНИЕ. Я мог бы написать свой собственный "читатель" или использовать "вокруг", но я бы предпочел сохранить его декларативным и позволить Moose выполнить эту работу. Я также мог бы создать новый объект только для хранения парных значений, но он кажется излишним только для двух значений.
Ответы
Ответ 1
Я бы не сказал, что это особенно элегантно, но оно работает...
use v5.14;
use warnings;
package Goose {
use Moose;
has max_things => (
is => 'rw',
isa => 'Int',
lazy => 1,
default => sub { shift->_build_maxes->max_things },
);
has max_pairs => (
is => 'rw',
isa => 'Int',
lazy => 1,
default => sub { shift->_build_maxes->max_pairs },
);
sub is_32_bit { 1 }
sub _build_maxes {
my $self = shift;
warn "Running builder...";
if( $self->is_32_bit ) {
$self->max_things(2**31);
$self->max_pairs(12345 * 2);
}
else {
$self->max_thing(2**63);
$self->max_pairs(23456 * 2);
}
$self; # helps chaining in the defaults above
}
}
my $goose = Goose->new;
say $goose->max_things;
say $goose->max_pairs;
Ответ 2
Я обычно обрабатываю это, направляя оба атрибута по третьему скрытому атрибуту:
has 'max_things' => (
'is' => "rw",
'isa' => "Int",
'lazy' => 1,
'default' => sub { (shift)->_both_maxes->{'max_things'} },
);
has 'max_pairs' => (
'is' => "rw",
'isa' => "Int",
'lazy' => 1,
'default' => sub { (shift)->_both_maxes->{'max_pairs'} },
);
has '_both_maxes' => (
'is' => "ro",
'isa' => "HashRef",
'lazy' => 1,
'builder' => "_build_both_maxes",
);
sub _build_both_maxes {
my $self = shift;
my ($max_things, $max_pairs);
if($self->is_32_bit) {
$max_things = 2 ** 31;
$max_pairs = 12345 * 2;
}
else {
$max_things = 2 ** 63;
$max_pairs = 23456 * 2;
}
return {
'max_things' => $max_things,
'max_pairs' => $max_pairs,
};
}
Ответ 3
Если они специально не должны быть разными атрибутами, я обычно использую собственные атрибуты атрибутов для "эмуляции" нескольких атрибутов:
has config => (
traits => ['Hash'],
is => 'bare',
isa => 'HashRef[Str]',
lazy => 1,
# returns a hashref of key/value config pairs
builder => 'load_config',
handles => {
has_author => [ exists => 'author' ],
author => [ get => 'author' ],
has_email => [ exists => 'email' ],
email => [ get => 'email' ],
},
);
Таким образом, дорогой строитель просто должен вернуть hashref с записями "автор" и "электронная почта"; атрибут будет генерировать методы доступа, которые затем выглядят и воспринимаются как те из отдельных атрибутов. Если вам нужно установить их отдельно в new(), это может быть не самый лучший вариант, хотя вы можете использовать BUILDARGS(), чтобы помочь; YMMV.
см. также http://wps.io/2012/05/simulating-multiple-lazy-attributes/