В 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/