¿Qué hace exactamente la "bendición" de Perl?
bless (7)
Entiendo que uno usa la palabra clave "bendiga" en Perl dentro del "nuevo" método de una clase:
sub new {
my $self = bless { };
return $self;
}
Pero, ¿qué es exactamente lo que "bendiga" a esa referencia hash?
En general, bless
asocia un objeto con una clase.
package MyClass;
my $object = { };
bless $object, "MyClass";
Ahora cuando invocas un método en $object
, Perl sabe qué paquete buscar para el método.
Si se omite el segundo argumento, como en su ejemplo, se utiliza el paquete / clase actual.
En aras de la claridad, su ejemplo podría escribirse de la siguiente manera:
sub new {
my $class = shift;
my $self = { };
bless $self, $class;
}
EDITAR: Vea la buena answer kixx para un poco más de detalle.
Esta función le dice a la entidad referenciada por REF que ahora es un objeto en el paquete CLASSNAME, o el paquete actual si se omite CLASSNAME. Se recomienda el uso de la forma de bendición de dos argumentos.
Ejemplo :
bless REF, CLASSNAME
bless REF
Valor de retorno
Esta función devuelve la referencia a un objeto bendito en CLASSNAME.
Ejemplo :
A continuación se muestra el código de ejemplo que muestra su uso básico, la referencia del objeto se crea al bendecir una referencia a la clase del paquete:
#!/usr/bin/perl
package Person;
sub new
{
my $class = shift;
my $self = {
_firstName => shift,
_lastName => shift,
_ssn => shift,
};
# Print all the values just for clarification.
print "First Name is $self->{_firstName}/n";
print "Last Name is $self->{_lastName}/n";
print "SSN is $self->{_ssn}/n";
bless $self, $class;
return $self;
}
Por ejemplo, si puede estar seguro de que cualquier objeto Bug va a ser un hash bendecido, puede (¡por fin!) Completar el código que falta en el método Bug :: print_me:
package Bug;
sub print_me
{
my ($self) = @_;
print "ID: $self->{id}/n";
print "$self->{descr}/n";
print "(Note: problem is fatal)/n" if $self->{type} eq "fatal";
}
Ahora, cada vez que se llama al método print_me a través de una referencia a cualquier hash que ha sido bendecido en la clase Bug, la variable $ self extrae la referencia que se pasó como primer argumento y luego las sentencias print acceden a las diversas entradas del hash bendecido.
Siguiendo este pensamiento para guiar el desarrollo orientado a objetos Perl.
Bendice asociar cualquier referencia de estructura de datos con una clase. Dado que Perl crea la estructura de herencia (en una especie de árbol) es fácil aprovechar el modelo de objetos para crear objetos para la composición.
Para esta asociación llamamos objeto, para desarrollar siempre debemos tener en cuenta que el estado interno del objeto y los comportamientos de clase están separados. Y puede bendecir / permitir cualquier referencia de datos para usar cualquier comportamiento de paquete / clase. Dado que el paquete puede comprender el estado "emocional" del objeto.
Versión corta: marca ese hash como adjunto al espacio de nombres del paquete actual (de modo que ese paquete proporciona su implementación de clase).
Voy a dar una respuesta aquí ya que los que están aquí no me han hecho clic.
La función de bendición de Perl asocia cualquier referencia a todas las funciones dentro de un paquete.
¿Por qué necesitaríamos esto?
Comencemos por expresar un ejemplo en JavaScript:
(() => {
''use strict'';
class Animal {
constructor(args) {
this.name = args.name;
this.sound = args.sound;
}
}
/* [WRONG] (global scope corruption)
* var animal = Animal({
* ''name'': ''Jeff'',
* ''sound'': ''bark''
* });
* console.log(animal.name + '', '' + animal.sound); // seems good
* console.log(window.name); // my window''s name is Jeff?
*/
// new is important!
var animal = new Animal(
''name'': ''Jeff'',
''sound'': ''bark''
);
console.log(animal.name + '', '' + animal.sound); // still fine.
console.log(window.name); // undefined
})();
Ahora vamos a desmantelar la construcción de clase y nos las arreglaremos sin ella:
(() => {
''use strict'';
var Animal = function(args) {
this.name = args.name;
this.sound = args.sound;
return this; // implicit context hashmap
};
// the "new" causes the Animal to be unbound from global context, and
// rebinds it to an empty hash map before being constructed. The state is
// now bound to animal, not the global scope.
var animal = new Animal({
''name'': ''Jeff'',
''sound'': ''bark''
});
console.log(animal.sound);
})();
La función toma una tabla hash de propiedades desordenadas (ya que no tiene sentido tener que escribir propiedades en un orden específico en lenguajes dinámicos en 2016) y devuelve una tabla hash con esas propiedades, o si olvidó poner la palabra clave nueva, devolverá todo el contexto global (por ejemplo, ventana en el navegador o global en nodejs).
Perl no tiene "esto" ni "nuevo" ni "clase", pero aún puede tener una función que se comporte de manera similar. No tendremos un constructor ni un prototipo, pero podremos crear nuevos animales a voluntad y modificar sus propiedades individuales.
# self contained scope
(sub {
my $Animal = (sub {
return {
''name'' => $_[0]{''name''},
''sound'' => $_[0]{''sound''}
};
});
my $animal = $Animal->({
''name'' => ''Jeff'',
''sound'' => ''bark''
});
print $animal->{sound};
})->();
Ahora, tenemos un problema: ¿y si queremos que el animal realice los sonidos por sí mismo en lugar de que nosotros imprimamos su voz? Es decir, queremos una función performSound que imprima el sonido del animal.
Una forma de hacerlo es enseñando a cada Animal individual cómo hacer su sonido. Esto significa que cada Cat tiene su propia función duplicada para ejecutar Sound.
# self contained scope
(sub {
my $Animal = (sub {
$name = $_[0]{''name''};
$sound = $_[0]{''sound''};
return {
''name'' => $name,
''sound'' => $sound,
''performSound'' => sub {
print $sound . "/n";
}
};
});
my $animal = $Animal->({
''name'' => ''Jeff'',
''sound'' => ''bark''
});
$animal->{''performSound''}();
})->();
Esto es malo porque performSound se coloca como un objeto de función completamente nuevo cada vez que se construye un animal. 10000 animales significa 10000 valores de rendimiento. Queremos tener una única función performSound que sea utilizada por todos los animales que busque su propio sonido y lo imprima.
(() => {
''use strict'';
/* a function that creates an Animal constructor which can be used to create animals */
var Animal = (() => {
/* function is important, as fat arrow does not have "this" and will not be bound to Animal. */
var InnerAnimal = function(args) {
this.name = args.name;
this.sound = args.sound;
};
/* defined once and all animals use the same single function call */
InnerAnimal.prototype.performSound = function() {
console.log(this.name);
};
return InnerAnimal;
})();
/* we''re gonna create an animal with arguments in different order
because we want to be edgy. */
var animal = new Animal({
''sound'': ''bark'',
''name'': ''Jeff''
});
animal.performSound(); // Jeff
})();
Aquí es donde el paralelo a Perl se detiene.
El nuevo operador de JavaScript no es opcional, sin él, "esto" dentro de los métodos de objeto corrompe el alcance global:
(() => {
// ''use strict''; // uncommenting this prevents corruption and raises an error instead.
var Person = function() {
this.name = "Sam";
};
// var wrong = Person(); // oops! we have overwritten window.name or global.main.
// console.log(window.name); // my window''s name is Sam?
var correct = new Person; // person''s name is actually stored in the person now.
})();
Queremos tener una función para cada Animal que busque el sonido de ese animal en lugar de codificarlo duro en la construcción.
Bendición nos permite usar un paquete como prototipo de objetos. De esta forma, el objeto es consciente del "paquete" al que se hace referencia y, a su vez, puede tener las funciones del paquete "llegar a" las instancias específicas que se crearon a partir del constructor de ese "objeto del paquete":
package Animal;
sub new {
my $packageRef = $_[0];
my $name = $_[1]->{''name''};
my $sound = $_[1]->{''sound''};
my $this = {
''name'' => $name,
''sound'' => $sound
};
bless($this, $packageRef);
return $this;
}
# all animals use the same performSound to look up their sound.
sub performSound {
my $this = shift;
my $sound = $this->{''sound''};
print $sound . "/n";
}
package main;
my $animal = Animal->new({
''name'' => ''Cat'',
''sound'' => ''meow''
});
$animal->performSound();
Resumen / TL; DR :
Perl no tiene "esto", "clase" ni "nuevo". bendecir un objeto a un paquete le da a ese objeto una referencia al paquete, y cuando llama a funciones en el paquete, sus argumentos serán compensados por 1 ranura, y el primer argumento ($ _ [0] o desplazamiento) será equivalente a javascript es "esto". A su vez, puedes simular de algún modo el modelo prototipo de JavaScript.
Lamentablemente, hace que sea imposible (a mi entender) crear "nuevas clases" en tiempo de ejecución, ya que necesita que cada "clase" tenga su propio paquete, mientras que en JavaScript, no necesita paquetes en absoluto, como palabra clave "nueva" crea un hashmap anónimo para que lo utilice como paquete en tiempo de ejecución al que puede agregar nuevas funciones y eliminar funciones sobre la marcha.
Hay algunas bibliotecas de Perl que crean sus propias formas de salvar esta limitación en la expresividad, como Moose.
¿Por qué la confusión? :
Por paquetes Nuestra intuición nos dice que vinculemos el objeto a un hashmap que contenga su ''prototipo''. Esto nos permite crear "paquetes" en tiempo de ejecución como JavaScript. Perl no tiene tanta flexibilidad (al menos no está integrado, tiene que inventarlo o obtenerlo de otros módulos) y, a su vez, la expresividad de su tiempo de ejecución se ve obstaculizada. Llamarlo "bendición" tampoco le favorece demasiado.
Lo que queremos hacer :
Algo así, pero tiene un enlace al mapa prototípico recursivo, y estará implícitamente ligado al prototipo en lugar de tener que hacerlo explícitamente.
Aquí hay un intento ingenuo: el problema es que "llamar" no sabe "lo que se llama", así que también puede ser una función perl universal "objectInvokeMethod (objeto, método)" que verifica si el objeto tiene el método , o su prototipo lo tiene, o su prototipo lo tiene, hasta que llega al final y lo encuentra o no (herencia prototípica). Perl tiene una agradable magia de evaluación para hacerlo, pero lo dejaré por algo que puedo intentar hacer más tarde.
De todos modos aquí está la idea:
(sub {
my $Animal = (sub {
my $AnimalPrototype = {
''performSound'' => sub {
return $_[0]->{''sound''};
}
};
my $call = sub {
my $this = $_[0];
my $proc = $_[1];
if (exists $this->{$proc}) {
return $this->{$proc}->();
} else {
return $this->{prototype}->{$proc}->($this, $proc);
}
};
return sub {
my $name = $_[0]->{name};
my $sound = $_[0]->{sound};
my $this = {
''this'' => $this,
''name'' => $name,
''sound'' => $sound,
''prototype'' => $AnimalPrototype,
''call'' => $call
};
};
})->();
my $animal = $Animal->({
''name'' => ''Jeff'',
''sound''=> ''bark''
});
print($animal->{call}($animal, ''performSound''));
})->();
De todos modos, con suerte, a alguien le resultará útil esta publicación.
bless
asocia una referencia con un paquete.
No importa a qué se refiera, puede ser un hash (caso más común), un array (no tan común), un escalar (generalmente esto indica un objeto de adentro hacia afuera ), a una expresión regular , subrutina o TYPEGLOB (consulte el libro Perl Oriented Perl: Una completa guía de conceptos y técnicas de programación de Damian Conway para ver ejemplos útiles) o incluso una referencia a un archivo o al identificador de directorio (caso menos común).
El efecto de la bless
es que le permite aplicar una sintaxis especial a la referencia bendita.
Por ejemplo, si se almacena una referencia bendecida en $obj
(asociada a bless
con el paquete "Class"), entonces $obj->foo(@args)
llamará a una subrutina foo
y pasará como primer argumento la referencia $obj
seguida del resto de los argumentos ( @args
). La subrutina debe definirse en el paquete "Clase". Si no hay una subrutina foo
en el paquete "Clase", se @ISA
una lista de otros paquetes (tomados de la matriz @ISA
en el paquete "Clase") y se @ISA
la primera subrutina encontrada.