Аксессоры Perl Moose, генерируемые на лету

См. следующий фрагмент кода Perl, основанный на Moose:

$BusinessClass->meta->add_attribute($Key => { is        => $rorw,
                                              isa       => $MooseType,
                                              lazy      => 0,
                                              required  => 0,
                                              reader    => sub { $_[0]->ORM->{$Key} },
                                              writer    => sub { $_[0]->ORM->newVal($Key, $_[1]) },
                                              predicate => "has_$Key",
                                            });

Я получаю сообщение об ошибке:

bad accessor/reader/writer/predicate/clearer format, must be a HASH ref at /usr/local/lib/perl5/site_perl/mach/5.20/Class/MOP/Class.pm line 899

Причина ошибки ясна: читатель и писатель должны быть строковыми именами функций.

Но что делать в данном конкретном случае? Я не хочу создавать новую функцию для каждого из сотни полей ORM (атрибут ORM здесь — привязанный хеш). Поэтому я не могу передать здесь строку, мне нужно закрытие.

Таким образом, мои потребности в кодировании привели к противоречию. Я не знаю, что делать.


Выше был фрагмент реального кода. Теперь я привожу минимальный пример:

#!/usr/bin/perl

my @Fields = qw( af sdaf gdsg ewwq fsf ); # pretend that we have 100 fields

# Imagine that this is a tied hash with 100 fields 
my %Data = map { $_ => rand } @Fields;

package Test;
use Moose;

foreach my $Key (@Fields) {
  __PACKAGE__->meta->add_attribute($Key => { is        => 'rw',
                                             isa       => 'Str',
                                             lazy      => 0,
                                             required  => 0,
                                             reader    => sub { $Data{$Key} },
                                             writer    => sub { $Data{$Key} = $_[1] },
                                           });
}

Запуск приводит к:

$ ./test.pl 
bad accessor/reader/writer/predicate/clearer format, must be a HASH ref at /usr/lib/i386-linux-gnu/perl5/5.22/Class/MOP/Class.pm line 899
    Class::MOP::Class::try {...}  at /usr/share/perl5/Try/Tiny.pm line 92
    eval {...} at /usr/share/perl5/Try/Tiny.pm line 83
    Try::Tiny::try('CODE(0x9dc6cec)', 'Try::Tiny::Catch=REF(0x9ea0c60)') called at /usr/lib/i386-linux-gnu/perl5/5.22/Class/MOP/Class.pm line 904
    Class::MOP::Class::_post_add_attribute('Moose::Meta::Class=HASH(0x9dc13f4)', 'Moose::Meta::Attribute=HASH(0x9dc6b5c)') called at /usr/lib/i386-linux-gnu/perl5/5.22/Class/MOP/Mixin/HasAttributes.pm line 39
    Class::MOP::Mixin::HasAttributes::add_attribute('Moose::Meta::Class=HASH(0x9dc13f4)', 'Moose::Meta::Attribute=HASH(0x9dc6b5c)') called at /usr/lib/i386-linux-gnu/perl5/5.22/Moose/Meta/Class.pm line 572
    Moose::Meta::Class::add_attribute('Moose::Meta::Class=HASH(0x9dc13f4)', 'af', 'HASH(0x9ea13a4)') called at test.pl line 18

Я не знаю, что делать (как создать «динамические» (замыкающие) аксессоры, не написав отдельную функцию для каждого из 100 полей?)


person porton    schedule 10.08.2016    source источник
comment
Не могли бы вы создать минимально воспроизводимый пример, чтобы мы могли это запустить?   -  person simbabque    schedule 10.08.2016
comment
Писатель должен писать непосредственно в хеш, а не в объект?   -  person simbabque    schedule 10.08.2016
comment
Я пытаюсь это сделать, но, кажется, я сломал Лося.   -  person simbabque    schedule 10.08.2016
comment
Обязательно ли использовать лося? Вам нужна швабра для чего-либо после того, как вы закончите создавать объекты? Или вы просто хотите использовать ООП-подход для доступа к вашему привязанному хешу?   -  person simbabque    schedule 10.08.2016
comment
Вы можете найти perlmonks.org/?node_id=969044 хорошее чтение.   -  person simbabque    schedule 10.08.2016
comment
@simbabque Да, мне приказали использовать Moose   -  person porton    schedule 10.08.2016
comment
@porton Тогда ваши заказы неверны ... Moose не имеет смысла, если все, что вы хотите сделать, это получить / установить значения в хеше.   -  person Sinan Ünür    schedule 10.08.2016
comment
Кем? Знают ли они, что это означает, или это просто мы делаем Moose здесь? Иногда споры окупаются, если вы действительно можете продемонстрировать свою правоту.   -  person simbabque    schedule 10.08.2016
comment
Вот еще один момент: эти очень странные и абстрактные проблемы, как правило, привлекают множество вопросов типа почему вы это делаете?. У меня самого было несколько таких вопросов, и я знаю, что иногда кажется, что этот сверхсложный подход — единственный способ сделать это. Но на самом деле это помогает сделать шаг назад и посмотреть, для чего вы действительно хотите его использовать, и как эта вещь должна вести себя снаружи, когда она будет готова. Часто таким образом можно найти другой, менее сложный способ. Поэтому, когда мы говорим не делайте этого, мы имеем в виду только хорошее. :)   -  person simbabque    schedule 10.08.2016
comment
@simbabque Это то, что мой босс поручил мне сделать. Других вариантов для меня нет (или сменить работу)   -  person porton    schedule 10.08.2016
comment
Это не похоже на счастливое место.   -  person simbabque    schedule 10.08.2016


Ответы (1)


Я думаю, что подобное изменение методов чтения и записи требует нездорового уровня безумия. Если хотите, взгляните на источник. код Class::MOP::Method::Accessor, который используется для создания методов доступа.

Вместо этого я предлагаю просто перезаписать (или прикрепить) функциональность к считывателям, сгенерированным Moose, используя модификатор метода around. Чтобы заставить это работать с подклассами, вы можете использовать Class::Method::Modifiers вместо Лося around.

package Foo::Subclass;
use Moose;
extends 'Foo';

package Foo;
use Moose;

package main;
require Class::Method::Modifiers; # no import because it would overwrite Moose

my @Fields = qw( af sdaf gdsg ewwq fsf );    # pretend that we have 100 fields

# Imagine that this is a tied hash with 100 fields
my %Data = map { $_ => rand } @Fields;

my $class = 'Foo::Subclass';
foreach my $Key (@Fields) {
    $class->meta->add_attribute(
        $Key => {
            is       => 'rw',
            isa      => 'Str',
            lazy     => 0,
            required => 0,
        }
    );

    Class::Method::Modifiers::around( "${class}::$Key", sub {
        my $orig = shift;
        my $self = shift;

        $self->$orig(@_);    # just so Moose is up to speed

        # writer
        $Data{$Key} = $_[0] if @_;

        return $Data{$Key};
    });
}

А потом запустить тест.

package main;
use Data::Printer;
use v5.10;

my $foo = Test->new;
say $foo->sdaf;
$foo->sdaf('foobar');
say $foo->sdaf;

p %Data;
p $foo;

Вот STDOUT/STDERR с моей машины.

{
    af     0.972962507120432,
    ewwq   0.959195914302605,
    fsf    0.719139421719849,
    gdsg   0.140205658312095,
    sdaf   "foobar"
}
Foo::Subclass  {
   Parents       Foo
    Linear @ISA   Foo::Subclass, Foo, Moose::Object
    public methods (6) : af, ewwq, fsf, gdsg, meta, sdaf
    private methods (0)
    internals: {
        sdaf   "foobar"
    }
}
0.885114977459551
foobar

Как видите, Moose на самом деле не знает о значениях внутри хэша, но если вы используете методы доступа, он будет читать и записывать их. Объект Moose будет постепенно заполняться новыми значениями при использовании средства записи, но в остальном значения внутри объекта Moose не имеют большого значения.

person simbabque    schedule 10.08.2016
comment
Отличное решение, но у меня оно не работает, потому что в реальном коде я вызываю $BusinessClass->meta->add_attribute(...); в базовом классе, обслуживающем производный класс. Предложенный вами around $Key вызывается в базовом классе и завершается ошибкой, потому что add_attribute() был вызван для другого (производного) класса, и поэтому в базовом классе нет атрибута $Key. Что делать? - person porton; 10.08.2016
comment
Это должно работать в дочерних классах. Я попробую, когда у меня будет компьютер. - person simbabque; 10.08.2016
comment
@porton Я адаптировал ответ. Test был переименован в Foo и теперь есть подкласс. Хитрость заключается в том, чтобы не использовать Moose around. - person simbabque; 10.08.2016