Entiendo que uno usa la palabra clave "bless" en Perl dentro del método "nuevo" de una clase:
sub new {
my $self = bless { };
return $self;
}
Pero, ¿qué hace exactamente "bendecir" a esa referencia hash?
Entiendo que uno usa la palabra clave "bless" en Perl dentro del método "nuevo" de una clase:
sub new {
my $self = bless { };
return $self;
}
Pero, ¿qué hace exactamente "bendecir" a esa referencia hash?
Respuestas:
En general, bless
asocia un objeto con una clase.
package MyClass;
my $object = { };
bless $object, "MyClass";
Ahora, cuando invocas un método $object
, Perl sabe en qué paquete buscar el método.
Si se omite el segundo argumento, como en su ejemplo, se usa el paquete / clase actual.
En aras de la claridad, su ejemplo podría estar escrito de la siguiente manera:
sub new {
my $class = shift;
my $self = { };
bless $self, $class;
}
EDITAR: Vea la buena respuesta de kixx para un poco más de detalle.
bless
asocia una referencia con un paquete.
No importa cuál sea la referencia, puede ser un hash (caso más común), una matriz (no tan común), un escalar (generalmente esto indica un objeto de adentro hacia afuera ), una expresión regular , subrutina o TYPEGLOB (vea el libro Perl orientado a objetos: una guía completa de conceptos y técnicas de programación de Damian Conway para ejemplos útiles) o incluso una referencia a un archivo o identificador de directorio (caso menos común).
El efecto bless
tiene es que le permite aplicar una sintaxis especial a la referencia bendecida.
Por ejemplo, si una referencia bendecida se almacena en $obj
(asociada bless
con el paquete "Clase"), $obj->foo(@args)
llamará a una subrutina foo
y pasará como primer argumento la referencia $obj
seguida por el resto de los argumentos ( @args
). La subrutina debe definirse en el paquete "Clase". Si no hay una subrutina foo
en el paquete "Clase", @ISA
se buscará una lista de otros paquetes (tomados de la matriz en el paquete "Clase") y se llamará a la primera subrutina foo
encontrada.
Esta función le dice a la entidad a la que REF hace referencia 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 bendecido 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;
}
Proporcionaré una respuesta aquí, ya que las de aquí no hicieron clic para mí.
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 expresando 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, eliminemos el constructo de la clase y lo hagamos sin él:
(() => {
'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 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 nueva palabra clave, 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: ¿qué pasa si queremos que el animal realice los sonidos por sí mismo en lugar de que nosotros imprimamos cuál es su voz? Es decir, queremos una función de realizar el sonido que imprima el sonido del animal.
Una forma de hacerlo es enseñando a cada animal individual cómo hacer que suene. Esto significa que cada gato tiene su propia función duplicada para realizar el sonido.
# 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 realizar sonidos. Queremos tener una sola función de realizar Sonido que sea utilizada por todos los animales que busquen su propio sonido y lo impriman.
(() => {
'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 se detiene el paralelo a Perl.
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 en la construcción.
La bendición nos permite usar un paquete como prototipo de objetos. De esta forma, el objeto conoce el "paquete" al que está "referenciado" y, a su vez, puede hacer que las funciones del paquete "lleguen" a las instancias específicas que se crearon a partir del constructor de ese "objeto de 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 se compensarán con 1 ranura, y el primer argumento ($ _ [0] o shift) será equivalente a "esto" de javascript. A su vez, puede simular de alguna manera el modelo prototipo de JavaScript.
Desafortunadamente, 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 use como un 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 reducir esta limitación en la expresividad, como Moose.
¿Por qué la confusión? :
Por los 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 puede hacerlo JavaScript. Perl no tiene tanta flexibilidad (al menos no está integrado, tiene que inventarlo u obtenerlo de otros módulos) y, a su vez, su expresividad en tiempo de ejecución se ve obstaculizada. Llamarlo "bendecir" tampoco le hace mucho favor.
Lo que queremos hacer :
Algo como esto, pero tiene un enlace recursivo al mapa prototipo y está implícitamente vinculado al prototipo en lugar de tener que hacerlo explícitamente.
Aquí hay un intento ingenuo: el problema es que "call" no sabe "qué lo llamó", por lo que bien podría 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 buena magia de evaluación para hacerlo, pero lo dejaré para algo que pueda 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, espero que alguien encuentre útil esta publicación.
my $o = bless {}, $anything;
bendecirá un objeto en la $anything
clase. Del mismo modo, {no strict 'refs'; *{$anything . '::somesub'} = sub {my $self = shift; return $self->{count}++};
creará un método llamado 'somesub' en la clase nombrada en $anything
. Todo esto es posible en tiempo de ejecución. "Posible", sin embargo, no hace que sea una buena práctica manejar el código de todos los días. Pero es útil en la construcción de sistemas de superposición de objetos como Moose o Moo.
unfortunately it makes it impossible(to my understanding) to create "new classes" at runtime
reclamo. Supongo que mi preocupación en última instancia se redujo a ser significativamente menos intuitivo para manipular / introspectar el sistema de paquetes en tiempo de ejecución, pero hasta ahora he fallado en mostrar algo que inherentemente no puede hacer. El sistema de paquetes parece admitir todas las herramientas necesarias para agregar / eliminar / inspeccionar / modificarse en tiempo de ejecución.
Junto con una serie de buenas respuestas, lo que distingue específicamente una bless
referencia ed es que elSV
para ello, recoge un adicional FLAGS
( OBJECT
) y unSTASH
perl -MDevel::Peek -wE'
package Pack { sub func { return { a=>1 } } };
package Class { sub new { return bless { A=>10 } } };
$vp = Pack::func(); print Dump $vp; say"---";
$obj = Class->new; print Dump $obj'
Impresiones, con las mismas partes (e irrelevantes para esto) suprimidas
SV = IV (0x12d5530) a 0x12d5540 REFCNT = 1 BANDERAS = (ROK) RV = 0x12a5a68 SV = PVHV (0x12ab980) a 0x12a5a68 REFCNT = 1 BANDERAS = (SHAREKEYS) ... SV = IV (0x12a5ce0) en 0x12a5cf0 REFCNT = 1 BANDERAS = (IOK, pIOK) IV = 1 --- SV = IV (0x12cb8b8) a 0x12cb8c8 REFCNT = 1 BANDERAS = (PADMY, ROK) RV = 0x12c26b0 SV = PVHV (0x12aba00) a 0x12c26b0 REFCNT = 1 BANDERAS = (OBJETO, SHAREKEYS) STASH = 0x12d5300 "Clase" ... SV = IV (0x12c26b8) a 0x12c26c8 REFCNT = 1 BANDERAS = (IOK, pIOK) IV = 10
Con eso se sabe que 1) es un objeto 2) a qué paquete pertenece, y esto informa su uso.
Por ejemplo, cuando se encuentra la desreferenciación en esa variable ( $obj->name
), se busca un sub con ese nombre en el paquete (o jerarquía), el objeto se pasa como primer argumento, etc.
Siguiendo este pensamiento para guiar el desarrollo orientado a objetos Perl.
Bless asocia cualquier referencia de estructura de datos con una clase. Dada la forma en 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 tenga en cuenta que el estado interno del objeto y los comportamientos de clase están separados. Y puede bendecir / permitir que cualquier referencia de datos use cualquier comportamiento de paquete / clase. Dado que el paquete puede entender el estado "emocional" del objeto.
Por ejemplo, si puede estar seguro de que cualquier objeto Bug será un hash bendecido, puede (¡por fin!) Completar el código faltante 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 mediante 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 declaraciones de impresión acceden a las diversas entradas del hash bendecido.