2010-08-04 21 views
12

Mi objetivo es ser capaz de utilizar $obj así:Perl: ¿cómo crear objetos sobre la marcha?

print $obj->hello() . $obj->{foo}; 

Y me gustaría crear un objeto en línea, tal vez el uso de algo como esto:

my $obj = (
    foo => 1, 
    hello => sub { return 'world' } 
); 

pero cuando intento para usar $obj como un objeto, me sale un error al decir que $ obj no ha sido bendecido. ¿Hay alguna clase base (como stdClass en PHP) que pueda usar para bendecir el hash para que pueda usarlo como un objeto?


Para aquellos que conocen JavaScript, yo estoy tratando de hacer lo siguiente, pero en Perl:

# JS CODE BELOW 
var obj = { foo: 1, hello: function() { return 'world' } }; 
echo obj.hello() + obj.foo; 
+0

métodos Perl no son campos en un hash - esto no es Java. – Ether

+0

JavaScript tampoco es Java. – cjm

Respuesta

14

Perl requeriría un poco de ayuda para hacer esto. Porque no considera las referencias de código almacenadas en hashes como "métodos". Los métodos se implementan como entradas en una tabla de símbolos de paquete.Perl es más orientado a clases que JavaScript, que orgullosamente proclama que es más orientado a objetos (en objetos individuales).

Para poder hacer esa funcionalidad, debería crear una clase que asigne referencias de esta manera. La forma de evitar los métodos en la tabla de símbolos es el método AUTOLOAD. Si un paquete contiene una subrutina AUTOLOAD, cuando se realiza una llamada a un objeto bendito que Perl no ha podido encontrar en la cadena de herencia, se llamará AUTOLOAD y configurar el paquete de ámbito (our) Variable $AUTOLOAD contendrá el completa nombre de la función.

Obtenemos el nombre del método llamado, obteniendo el último nodo (después del último '::') del nombre secundario completo. Miramos para ver si hay un coderef en esa ubicación, y si existe, podemos devolverlo.

package AutoObject; 

use strict; 
use warnings; 
use Carp; 
use Params::Util qw<_CODE>; 
our $AUTOLOAD; 

sub AUTOLOAD { 
    my $method_name = substr($AUTOLOAD, index($AUTOLOAD, '::') + 2); 
    my ($self) = @_; 
    my $meth  = _CODE($self->{$method_name}); 
    unless ($meth) { 
     Carp::croak("object does not support method='$method_name'!"); 
    } 
    goto &$meth; 
} 


1; 

allí tendría que bendecir el objeto en esa clase:

package main; 

my $obj 
    = bless { foo => 1 
     , hello => sub { return 'world' } 
     }, 'AutoObject'; 

print $obj->hello(); 

Normalmente, el comportamiento "cemento" en un sub AUTOLOAD. Es decir, creo entradas en la tabla de símbolos del paquete para evitar AUTOLOAD la próxima vez. Pero eso es generalmente para un comportamiento de clase razonablemente definido.

También diseñé un QuickClass que crea un paquete para cada objeto declarado, pero que contiene una gran cantidad de disputas de tablas de símbolos que ahora probablemente se hagan mejor con Class::MOP.


Dada la sugerencia de Eric Strom, podría agregar el siguiente código en el paquete AutoObject. El sub import se llamaría en cualquier momento alguien use -d AutoObject (con el parámetro 'object').

# Definition: 
sub object ($) { return bless $_[0], __PACKAGE__; }; 

sub import { # gets called when Perl reads 'use AutoObject;' 
    shift; # my name 
    return unless $_[0] eq 'object'; # object is it's only export 
    use Symbol; 
    *{ Symbol::qualify_to_reference('object', scalar caller()) } 
     = \&object 
     ; 
} 

Y luego, cuando se quería crear un "objeto literal", sólo podía hacer:

use AutoObject qw<object>; 

Y la expresión sería:

object { foo => 1, hello => sub { return 'world' } }; 

Incluso se puede hacer :

object { name => 'World' 
     , hello => sub { return "Hello, $_[0]->{name}"; } 
     }->hello() 
     ; 

Y usted tiene un "objeto expresión literal ". Tal vez el módulo sea mejor llamado Object::Literal.

+0

Este es un truco útil, en realidad. Podría hacer un buen uso de esto, si alguna vez usé OOP en Perl. –

+0

Puede hacer que el código de llamada sea un poco más claro al ocultar la bendición en un sub: 'subobjeto {bless $ _ [0] => 'AutoObject'}' y luego 'my $ obj = object {...};' –

+0

@Eric Strom: eso tiene sentido. Pero aquí está mi pensamiento: el modismo de JavaScript es básicamente un "objeto literal". En Perl, el "objeto literal" es (a menudo) "bendecir {...}, 'MyClass''. * Por supuesto *, dado que 'bless' es solo un func, no hay ninguna razón para que 'object {...} 'no sea un literal mejor. Cambiaré mi código. – Axeman

2

$obj sería un escalar, así que lo que asignar al que tiene que ser un escalar como bien. Usted podría decir

my %obj = (foo => 1, hello => sub { return 'world' }); 

o

my $obj = { foo => 1, hello => sub { return 'world' }}; 

Esta última, con las llaves, crea una referencia a un hash (que es un escalar, por lo que puede entrar en $obj). Sin embargo, para llegar al material dentro de una referencia hash, debe usar el operador de flecha. Algo así como $obj->{foo} o &{$obj->{hello}}.

a menos que necesite tener listas de hash o algo así, por lo general es mejor utilizar el primer método.

De cualquier manera, usted no será capaz de decir $obj->hello(). Perl usa esa sintaxis para su propio sabor de OOP, que tendría la función hello en un paquete separado en el que su referencia ha sido bless ed. Al igual que:

package example; 
sub new {} { my $result = {}; return bless $result, 'example' } 
sub hello { return 'world' } 

package main; 
my $obj = example->new(); 

Como se puede ver, los métodos que puede llamar ya están definidos, y no es trivial para añadir más. Hay métodos mágicos que puedes usar para hacer tal cosa, pero en realidad, no vale la pena. &{$obj{hello}} (o &{$obj->{hello}} como referencia) es un esfuerzo menor que tratar de hacer que Perl funcione como Javascript.

+0

Bueno, esto me da material de lectura para el resto del día ... – Tom

0

Métodos en Perl no son propiedades del objeto como si estuvieran en Python. Los métodos son funciones normales de funciones regulares en un paquete asociado con el objeto. Funciones regulares tomando un argumento adicional para la auto referencia.

No puede haber creado dinámicamente funciones como métodos.

Aquí es una cita de perlobj perldoc:

1. An object is simply a reference that happens to know which class it 
     belongs to. 

    2. A class is simply a package that happens to provide methods to deal 
     with object references. 

    3. A method is simply a subroutine that expects an object reference 
     (or a package name, for class methods) as the first argument. 

Ah, y bendiga() es la forma que establece la conexión entre la referencia y el paquete.

+0

No estoy seguro de lo que tiene en mente diciendo que * "No puede haber creado dinámicamente funciones como métodos." * Se pueden generar funciones dinámicamente e insertarlas en un paquete. Muchos módulos de CPAN hacen esto. Como ejemplo bastante simple, vea Getopt :: Long :: Descriptive. Para cada analizador de opciones solicitado por el usuario, genera un nuevo nombre de clase. En ese paquete, instala un getter para cada opción de línea de comandos definida por el usuario. – FMc

+0

Quiero decir que no puede agregar un método de la forma en que lo desea. La inserción de funciones dinámicas es mucho más meticuloso que esto ("no ref terminante; * {" $ paquete \ :: función "} = sub {...};", ¿alguien? Gah!) Y no debe emprenderse a la ligera. – Arkadiy

1

En cualquier función en la que esté creando el objeto, debe llamar al bless en su objeto para habilitar la llamada al método.

Por ejemplo:

package MyClass; 

sub new 
{ 
    my $obj = { 
    foo => 1 
    }; 

    return bless($obj, "MyClass"); 
} 

sub hello 
{ 
    my $self = shift; 
    # Do stuff, including shifting off other arguments if needed 
} 

package main; 
my $obj = MyClass::new(); 

print "Foo: " . $obj->{foo} . "\n"; 
$obj->hello(); 

EDIT: Si usted quiere ser capaz de utilizar referencias a subrutinas para proporcionar funcionalidad dinámica para sus objetos ...

En primer lugar, puede crear su código de referencia como tal (a menos de este ejemplo constructor de hash):

my $obj = { 
    foo => 1, 
    hello => sub { print "Hello\n"; }, 
} 

continuación, se puede invocar como esto:

my $obj = MyClass::new(); # or whatever 
$obj->{hello}->(@myArguments); 

Un poco engorroso, pero trabajos. (Puede que ni siquiera necesita la segunda flecha, pero no estoy seguro.)

2

se escribe un poco diferente en Perl:

my $obj = { foo => 1, hello => sub { return "world" } }; 
print $obj->{hello}() . $obj->{foo}; 

Sin embargo, el código es incómoda. La advertencia que vio sobre la referencia que no fue bendecida le está diciendo que sus objetos no están implementados en the way Perl expects. El operador bless marca un objeto con el paquete en el que comenzar a buscar sus métodos.

Dinos que deseas hacer en términos de tu dominio de problema, y ​​podemos ofrecer sugerencias para un enfoque más natural en Perl.

4

Un enfoque más Perlish es crear un espacio de nombre separado para los métodos deseados de su objeto y bless el objeto para que esos métodos estén disponibles para su objeto. El código para hacer esto todavía puede ser bastante succint.

my $obj = bless { foo => 1 }, "bar"; 
sub bar::hello { return 'world' }; 

Como gbacon sugiere, si usted está dispuesto a escribir $obj->{hello}->() en lugar de $obj->hello(), puede omitir la operación bendiga.

my $obj = { foo => 1, hello => sub { return 'world' } }; 
0

Recomiendo usar Class :: Struct como se explica en la página del manual de perltoot.

En lugar de parafrasear la documentación, permítanme citar como se explicó bien cómo funciona esto:

"Lo que hace es que proporciona una forma de 'declarar' una clase que tienen fines cuyos campos son de una específica La función que hace esto se llama, no sorprendentemente, struct(). Debido a que las estructuras o registros no son tipos básicos en Perl, cada vez que desee crear una clase para proporcionar un objeto de datos similar a un registro, usted mismo debe defina un método nuevo(), más métodos de acceso a datos separados para cada uno de los campos de ese registro. Pronto se aburrirá con este proceso. La función Class :: Struct :: struct() alivia este tedio. "

Aún citando el documento es una forma ejemplo de cómo ponerlo en práctica:

use Class::Struct qw(struct); 
use Jobbie; # user-defined; see below 
struct 'Fred' => { 
    one  => '$', 
    many  => '@', 
    profession => 'Jobbie', # does not call Jobbie->new() 
}; 
$ob = Fred->new(profession => Jobbie->new()); 
$ob->one("hmmmm"); 
$ob->many(0, "here"); 
$ob->many(1, "you"); 
$ob->many(2, "go"); 
print "Just set: ", $ob->many(2), "\n"; 
$ob->profession->salary(10_000); 
Cuestiones relacionadas