Pregunta ¿Cómo creo una clase en memoria y luego la incluyo en Perl?


Así que estoy jugando con algo de magia negra en Perl (eventualmente todos lo hacemos :-) y estoy un poco confundido en cuanto a cómo se supone que debo estar haciendo todo esto. Esto es lo que estoy empezando con:

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;

Recibo los siguientes errores (dependiendo de si mi línea de requerimiento está comentada):

Con requiere:

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

sin:

$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.

Cualquier hechicero que ya conozca algo de esta magia negra: ¡por favor responda! Me encantaría aprender más sobre este arcano :-)

También tenga en cuenta: Sé que puedo hacer este tipo de cosas con Moose y otros módulos de ayuda más ligeros, principalmente estoy tratando de aprender, por lo que las recomendaciones para usar tal y cual módulo no obtendrán mis votos :-)

Actualizar: Ok, creo que originalmente no estaba muy claro con mi pregunta. Básicamente, quiero generar una clase de Perl con una cadena (que manipularé y realizaré la interpolación) basada en una estructura de datos externa. Me imagino que pasar de lo que tengo aquí (una vez que funciona) a eso no debería ser demasiado difícil.


5


origen


Respuestas:


Aquí hay una versión que funciona:

#!/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;

los @INC gancho obtiene el nombre del archivo (o cadena pasada a require) como segundo argumento, y se llama cada tiempo hay un require o use. Así que tienes que verificar para asegurarte de que estamos intentando cargar $classname e ignora todos los demás casos, en cuyo caso perl continúa hacia abajo a lo largo de @INC. Alternativamente, puede poner el gancho al final de @INC. Esta fue la causa de sus errores de recursión.

ETA: En mi humilde opinión, una forma mucho mejor de lograr esto sería simplemente construir dinámicamente la tabla de símbolos, en lugar de generar código como una cadena. Por ejemplo:

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

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

No use o require Es necesario, ni meterse con el mal. @INC manos.


9



Hago esto:

use MooseX::Declare;

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

Luego puedes usar $ class como cualquier otra metaclase:

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

etc.

Si desea generar clases dinámicamente en tiempo de ejecución, debe crear la metaclase adecuada, crear instancias y luego usar la instancia de metaclase para generar instancias. OO básico. Class :: MOP maneja todos los detalles por usted:

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

Si quiere hacerlo usted mismo para que pueda perder su tiempo depurando algo, tal vez intente:

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;

6



Para un ejemplo simple de cómo hacer esto, lee la fuente de Class :: Struct.

Sin embargo, si necesitara la capacidad de crear clases dinámicamente para algún código de producción, vería MooseX :: Declare, como sugiere jrockway.


0



Una clase de Perl es poco más que una estructura de datos (generalmente un hashref) que ha sido bendecido en un paquete en el que una o más clases Se definen los métodos.

Es ciertamente posible definir múltiples espacios de nombres de paquetes en uno archivo; No veo por qué esto no sería posible en una eval construir que se compila en tiempo de ejecución (ver perlfunc para los dos diferentes eval formas).

#!/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;

-3