¿Qué hace exactamente la "bendición" de Perl?


142

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?


2
Ver "Bendice a mis referentes" de 1999. Parece bastante detallado. (La entrada manual de Perl no tiene mucho que decir al respecto, desafortunadamente.)
Jon Skeet

Respuestas:


143

En general, blessasocia 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.


79

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 blesscon el paquete "Clase"), $obj->foo(@args)llamará a una subrutina fooy pasará como primer argumento la referencia $objseguida por el resto de los argumentos ( @args). La subrutina debe definirse en el paquete "Clase". Si no hay una subrutina fooen el paquete "Clase", @ISAse buscará una lista de otros paquetes (tomados de la matriz en el paquete "Clase") y se llamará a la primera subrutina fooencontrada.


16
Su declaración inicial es incorrecta. Sí, bless toma una referencia como primer argumento, pero es la variable referente la que se bendice, no la referencia en sí misma. $ perl -le 'sub Somepackage :: foo {42}; % h = (); $ h = \% h; bendiga $ h, "Somepackage"; $ j = \% h; print $ j-> UNIVERSAL :: can ("foo") -> () '42
converter42

1
La explicación de Kixx es integral. No debemos molestarnos con la elección del convertidor en minucias teóricas.
Beato Geek

19
@ Bendito Geek, no son minucias teóricas. La diferencia tiene aplicaciones prácticas.
ikegami

3
El viejo enlace perlfoundation.org para "objeto de adentro hacia afuera" está, en el mejor de los casos, detrás de un muro de inicio de sesión ahora. El enlace de Archive.org del original está aquí .
ruffin

2
Quizás esto sirva en lugar del enlace roto que @harmic comentó en: perldoc.perl.org/perlobj.html#Inside-Out-objects
Rhubbarb

9

Versión corta: marca ese hash como adjunto al espacio de nombres del paquete actual (para que ese paquete proporcione su implementación de clase).


7

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;
}

4

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.


No es imposible crear nuevas clases en tiempo de ejecución. my $o = bless {}, $anything;bendecirá un objeto en la $anythingclase. 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.
DavidO

interesante, entonces usted dice que puedo bendecir a un referente en una clase cuyo nombre se decide en tiempo de ejecución. Parece interesante y anula mi unfortunately it makes it impossible(to my understanding) to create "new classes" at runtimereclamo. 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.
Dmitry

Esto es correcto; puede manipular la tabla de símbolos de Perl mediante programación y, por lo tanto, puede manipular los paquetes de Perl y los miembros de un paquete en tiempo de ejecución, incluso sin haber declarado "paquete Foo" en ningún lado. Inspección y manipulación de tablas de símbolos en tiempo de ejecución, semántica de AUTOLOAD, atributos de subrutina, vinculación de variables a clases ... hay muchas maneras de pasar por alto. Algunos de ellos son útiles para autogenerar API, herramientas de validación, autodocumentar API; No podemos predecir todos los casos de uso. Pegarse un tiro en el pie también es un posible resultado de tal engaño.
DavidO

4

Junto con una serie de buenas respuestas, lo que distingue específicamente una blessreferencia 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.


1

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.


Aquí están los mismos anuncios sobre cómo Perl funciona con espacios de nombres de paquetes y cómo funciona con estados registrados en su espacio de nombres. Porque existen pragmas como use namespace :: clean. Pero trate de mantener las cosas lo más simples posible.
Steven Koch

-9

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.


@darch ¿De qué fuente fue plagiada esta respuesta?
Anderson Green
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.