Как мне создать класс в памяти и затем включить его в Perl? - PullRequest
5 голосов
/ 15 июля 2009

Так что я играю с какой-то черной магией в Perl (в конце концов, мы все так и делаем :-), и я немного растерялся относительно того, как именно я должен делать все это. Вот с чего я начинаю:

use strict;
use warnings;
use feature ':5.10';
my $classname = 'Frew';
my $foo = bless({ foo => 'bar' }, $classname);
no strict;
*{"$classname\::INC"} = sub {
      use strict;
      my $data =  qq[
         package $classname
         warn 'test';
         sub foo {
            print "test?";
         }
      ];
      open my $fh, '<', \$data;
      return $fh;
   };
use strict;
unshift @INC, $foo;
require $foo;
use Data::Dumper;
warn Dumper(\@INC);
$classname->foo;

Я получаю следующие ошибки (в зависимости от того, закомментирована ли моя строка запроса):

С требуется:

Recursive call to Perl_load_module in PerlIO_find_layer at crazy.pl line 16.
BEGIN failed--compilation aborted.

без

$VAR1 = [
      bless( {
               'foo' => 'bar'
             }, 'Frew' ),
      'C:/usr/site/lib',
      'C:/usr/lib',
      '.'
    ];
Can't locate object method "foo" via package "Frew" at crazy.pl line 24.

Любые волшебники, которые уже знают немного этой черной магии: пожалуйста, ответьте! Я хотел бы узнать больше об этой тайне: -)

Также обратите внимание: я знаю, что могу делать такие вещи с Moose и другими более легкими вспомогательными модулями, я в основном пытаюсь учиться, поэтому рекомендации по использованию такого-и-такого модуля не получат моих голосов :-)

Обновление : Ладно, я не совсем понял с самого начала свой вопрос. Я в основном хочу создать класс Perl со строкой (которой я буду манипулировать и выполнять интерполяцию) на основе внешней структуры данных. Я полагаю, что переход от того, что у меня есть здесь (когда это работает) к этому, не должно быть слишком сложным.

Ответы [ 4 ]

10 голосов
/ 15 июля 2009

Вот версия, которая работает:

#!/usr/bin/perl

use strict;
use warnings;

my $class = 'Frew';

{
    no strict 'refs';
    *{ "${class}::INC" } = sub {
        my ($self, $req) = @_;
        return unless $req eq  $class;
        my $data = qq{
            package $class;
            sub foo { print "test!\n" };
            1;
        };
        open my $fh, '<', \$data;
        return $fh;
    };
}

my $foo = bless { }, $class;
unshift @INC, $foo;

require $class;
$class->foo;

Хук @INC получает имя файла (или строку, переданную require) в качестве второго аргумента, и он вызывается каждый каждый раз, когда require или use , Таким образом, вы должны проверить, чтобы убедиться, что мы пытаемся загрузить $classname и игнорировать все другие случаи, в этом случае perl продолжается вниз по @INC. Кроме того, вы можете поставить крюк в конце @INC. Это и стало причиной ваших ошибок рекурсии.

ETA: ИМХО, гораздо лучший способ добиться этого - просто динамически создавать таблицу символов, а не генерировать код в виде строки. Например:

no strict 'refs';
*{ "${class}::foo" } = sub { print "test!\n" };
*{ "${class}::new" } = sub { return bless { }, $class };

my $foo = $class->new;
$foo->foo;

Нет use или require необходимо, и не нужно связываться со злыми @INC крючками.

6 голосов
/ 15 июля 2009

Я делаю это:

use MooseX::Declare;

my $class = class {
    has 'foo' => (is => 'ro', isa => 'Str', required => 1);
    method bar() {
        say "Hello, world; foo is ", $self->foo;
    }
};

Тогда вы можете использовать $ class, как и любой другой метакласс:

my $instance = $class->name->new( foo => 'foo bar' );
$instance->foo; # foo-bar
$instance->bar; # Hello, world; foo is foo-bar

и т.д.

Если вы хотите динамически генерировать классы во время выполнения, вам нужно создать соответствующий метакласс, создать его экземпляр, а затем использовать экземпляр метакласса для генерации экземпляров. Базовый ОО. Class :: MOP обрабатывает все детали для вас:

my $class = Class::MOP::Class->create_anon_class;
$class->add_method( foo => sub { say "Hello from foo" } );
my $instance = $class->new_object;
...

Если вы хотите сделать это самостоятельно, чтобы потратить время на отладку чего-либо, возможно, попробуйте:

sub generate_class_name {
    state $i = 0;
    return '__ANON__::'. $i++;
}

my $classname = generate_class_name();
eval qq{
    package $classname;
    sub new { my \$class = shift; bless {} => \$class }
    ...
};

my $instance = $classname->new;
0 голосов
/ 15 июля 2009

Для простого примера того, как это сделать, прочитайте источник Class :: Struct .

Однако, если бы мне понадобилась возможность динамически создавать классы для некоторого производственного кода, я бы посмотрел на MooseX :: Declare, как это предлагает jrockway.

0 голосов
/ 15 июля 2009

Класс Perl - это нечто большее, чем структура данных который был благословлен в пакет, в котором один или несколько классов методы определены.

Конечно, можно определить несколько пространств имен пакета в одном файл; Я не понимаю, почему это невозможно в конструкции eval который компилируется во время выполнения (см. perlfunc для двух разных eval формы).

#!/usr/bin/perl

use 5.010;
use strict;
use warnings;
use Data::Dumper;

eval q[
    package Foo;
    sub new {
        my ( $class, %args ) = @_;
        my $self = bless { %args }, $class;
        return $self;
    }
    1;
];
die $@ if $@;

my $foo = Foo->new(bar => 1, baz => 2) or die;

say Dumper $foo;
...