O que exatamente a “benção” de Perl faz?


142

Entendo que alguém use a palavra-chave "bless" no Perl dentro do método "novo" de uma classe:

sub new {
    my $self = bless { };
    return $self;
}    

Mas o que exatamente "abençoar" está fazendo com essa referência de hash?


2
Veja "Bless My Referents" de 1999. Parece bem detalhado. (A entrada manual Perl não tem muito a dizer sobre ele, infelizmente.)
Jon Skeet

Respostas:


143

Em geral, blessassocia um objeto a uma classe.

package MyClass;
my $object = { };
bless $object, "MyClass";

Agora, quando você invoca um método $object, o Perl sabe em qual pacote procurar o método.

Se o segundo argumento for omitido, como no seu exemplo, o pacote / classe atual será usado.

Por uma questão de clareza, seu exemplo pode ser escrito da seguinte maneira:

sub new { 
  my $class = shift; 
  my $self = { }; 
  bless $self, $class; 
} 

EDIT: Veja a boa resposta do kixx para um pouco mais de detalhes.


79

bless associa uma referência a um pacote.

Não importa para que serve a referência, pode ser um hash (caso mais comum), uma matriz (não tão comum), um escalar (geralmente isso indica um objeto de dentro para fora ), uma expressão regular , sub-rotina ou TYPEGLOB (consulte o livro Perl orientado a objetos: um guia abrangente de conceitos e técnicas de programação de Damian Conway para obter exemplos úteis) ou até mesmo uma referência a um identificador de arquivo ou diretório (caso menos comum).

O efeito bless-ing tem é que permite aplicar sintaxe especial à referência abençoada.

Por exemplo, se uma referência abençoada for armazenada em $obj(associada ao blesspacote "Class"), $obj->foo(@args)ela chamará uma sub-rotina fooe passará como primeiro argumento a referência $objseguida pelo restante dos argumentos ( @args). A sub-rotina deve ser definida no pacote "Class". Se não houver sub-rotina foono pacote "Class", será pesquisada uma lista de outros pacotes (obtidos da matriz @ISAno pacote "Class") e a primeira sub-rotina fooencontrada será chamada.


16
Sua declaração inicial está incorreta. Sim, abençoe leva uma referência como seu primeiro argumento, mas é a variável referente que é abençoada, não a referência em si. $ perl -le 'sub Somepackage :: foo {42}; % h = (); $ h = \% h; abençoe $ h, "Algum pacote"; $ j = \% h; print $ j-> UNIVERSAL :: can ("foo") -> () '42
converter42

1
A explicação de Kixx é abrangente. Não devemos nos preocupar com a escolha do conversor em minúcias teóricas.
Blessed Geek

19
@ Geek blessed, não é minúcia teórica. A diferença tem aplicações práticas.
Ikegami

3
O link antigo do perlfoundation.org para "objeto de dentro para fora" está, na melhor das hipóteses, atrás de uma parede de login agora. O link Archive.org do original está aqui .
Ruffin

2
Talvez isso vai servir no lugar da ligação @harmic quebrado comentou: perldoc.perl.org/perlobj.html#Inside-Out-objects
Rhubbarb

9

Versão curta: está marcando esse hash como anexado ao espaço para nome do pacote atual (para que esse pacote forneça sua implementação de classe).


7

Esta função informa à entidade referenciada pelo REF que agora é um objeto no pacote CLASSNAME ou o pacote atual se CLASSNAME for omitido. Recomenda-se o uso da forma de abençoar com dois argumentos.

Exemplo :

bless REF, CLASSNAME
bless REF

Valor de retorno

Esta função retorna a referência a um objeto abençoado em CLASSNAME.

Exemplo :

A seguir, o código de exemplo que mostra seu uso básico, a referência do objeto é criada abençoando uma referência à classe do pacote -

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

Fornecerei uma resposta aqui, já que as que não foram encontradas aqui.

A função bless do Perl associa qualquer referência a todas as funções dentro de um pacote.

Por que precisamos disso?

Vamos começar expressando um exemplo em 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
})();

Agora vamos remover a construção da classe e passar sem ela:

(() => {
    '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);    
})();

A função pega uma tabela hash de propriedades não ordenadas (já que não faz sentido escrever propriedades em uma ordem específica em idiomas dinâmicos em 2016) e retorna uma tabela hash com essas propriedades, ou se você esqueceu de colocar a nova palavra-chave, ela retornará todo o contexto global (por exemplo, janela no navegador ou global no nodejs).

Perl não tem "isso" nem "novo" nem "classe", mas ainda pode ter uma função que se comporta de maneira semelhante. Não teremos construtor nem protótipo, mas poderemos criar novos animais à vontade e modificar suas propriedades individuais.

# self contained scope 
(sub {
    my $Animal = (sub {
        return {
            'name' => $_[0]{'name'},
            'sound' => $_[0]{'sound'}
        };
    });

    my $animal = $Animal->({
        'name' => 'Jeff',
        'sound' => 'bark'
    });

    print $animal->{sound};
})->();

Agora, temos um problema: e se queremos que o animal faça os sons sozinhos, em vez de imprimirmos a voz deles. Ou seja, queremos uma função performSound que imprima o som do animal.

Uma maneira de fazer isso é ensinando cada animal individualmente como fazer o seu som. Isso significa que cada gato tem sua própria função duplicada para executar o som.

# 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'}();
})->();

Isso é ruim porque performSound é colocado como um objeto de função completamente novo toda vez que um animal é construído. 10000 animais significa 10000 performSounds. Queremos que uma única função performSound seja usada por todos os animais que procure seu próprio som e o 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
})();

Aqui é onde o paralelo ao Perl meio que para.

O novo operador do JavaScript não é opcional, sem ele, "this" dentro dos métodos de objeto corrompe o escopo 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 ter uma função para cada animal que procure o som do animal em vez de codificá-lo na construção.

Bênção nos permite usar um pacote como o protótipo de objetos. Dessa forma, o objeto está ciente do "pacote" ao qual é "referenciado" e, por sua vez, pode fazer com que as funções no pacote "alcancem" as instâncias específicas que foram criadas a partir do construtor desse "objeto de pacote":

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();

Resumo / TL; DR :

Perl não tem "isso", "classe", nem "novo". abençoar um objeto para um pacote fornece a ele uma referência ao pacote e, quando ele chama funções no pacote, seus argumentos serão deslocados em 1 slot, e o primeiro argumento ($ _ [0] ou shift) será equivalente a javascript é "isso". Por sua vez, você pode simular um pouco o modelo de protótipo do JavaScript.

Infelizmente, torna impossível (no meu entender) criar "novas classes" em tempo de execução, pois você precisa que cada "classe" tenha seu próprio pacote, enquanto que em javascript, você não precisa de pacotes, como palavra-chave "nova" cria um hashmap anônimo para você usar como um pacote em tempo de execução ao qual você pode adicionar novas funções e remover funções rapidamente.

Existem algumas bibliotecas Perl que criam suas próprias maneiras de superar essa limitação na expressividade, como o Moose.

Por que a confusão? :

Por causa dos pacotes. Nossa intuição nos diz para vincular o objeto a um hashmap contendo seu 'protótipo. Isso nos permite criar "pacotes" em tempo de execução, como o JavaScript pode. O Perl não tem essa flexibilidade (pelo menos não está embutido, é necessário inventá-lo ou obtê-lo de outros módulos) e, por sua vez, sua expressividade no tempo de execução é prejudicada. Chamar isso de "abençoar" também não ajuda muito.

O que queremos fazer :

Algo parecido com isto, mas tem ligação com o mapa do protótipo recursivo e está implicitamente vinculado ao protótipo, em vez de ter que fazê-lo explicitamente.

Aqui está uma tentativa ingênua: o problema é que "call" não sabe "o que chamou", portanto pode ser também uma função perl universal "objectInvokeMethod (objeto, método)" que verifica se o objeto possui o método , ou seu protótipo possui, ou seu protótipo, até que chegue ao fim e o encontre ou não (herança prototípica). Perl tem uma boa magia de avaliação para fazer isso, mas deixarei isso para algo que eu possa tentar fazer mais tarde.

Enfim, aqui está a idéia:

(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 qualquer forma, espero que alguém ache este post útil.


Não é impossível criar novas classes em tempo de execução. my $o = bless {}, $anything;abençoará um objeto na $anythingclasse. Da mesma forma, {no strict 'refs'; *{$anything . '::somesub'} = sub {my $self = shift; return $self->{count}++};criará um método chamado 'somesub' na classe nomeada em $anything. Tudo isso é possível em tempo de execução. "Possível", no entanto, não torna uma boa prática usar o código diário. Mas é útil na construção de sistemas de sobreposição de objetos, como Moose ou Moo.
DaVido

interessante, então você está dizendo que eu posso abençoar um referente em uma classe cujo nome é decidido em tempo de execução. Parece interessante e anula minha unfortunately it makes it impossible(to my understanding) to create "new classes" at runtimereivindicação. Eu acho que minha preocupação acabou se resumindo ao fato de ser significativamente menos intuitivo manipular / introspectar o sistema de pacotes em tempo de execução, mas até agora eu falhei em mostrar qualquer coisa que inerentemente não possa fazer. O sistema de pacotes parece suportar todas as ferramentas necessárias para adicionar / remover / inspecionar / modificar a si próprio em tempo de execução.
Dmitry

Isto está certo; você pode manipular a tabela de símbolos do Perl programaticamente e, portanto, pode manipular os pacotes do Perl e os membros de um pacote em tempo de execução, mesmo sem ter declarado o "pacote Foo" em qualquer lugar. Inspeção e manipulação da tabela de símbolos em tempo de execução, semântica AUTOLOAD, atributos de sub-rotina, vinculação de variáveis ​​a classes ... existem muitas maneiras de entender o problema. Alguns deles são úteis para gerar APIs automaticamente, ferramentas de validação, APIs de documentação automática; não podemos prever todos os casos de uso. Dar um tiro no próprio pé também é um resultado possível desse truque.
DaVido

4

Juntamente com uma série de boas respostas, o que distingue especificamente uma blessreferência -ed é que, SV para isso, ela recebe um adicional FLAGS( OBJECT) e umSTASH

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'

Imprime, com as mesmas peças (e irrelevantes para isso) suprimidas

SV = IV (0x12d5530) em 0x12d5540
  REFCNT = 1
  BANDEIRAS = (ROK)
  RV = 0x12a5a68
  SV = PVHV (0x12ab980) em 0x12a5a68
    REFCNT = 1
    BANDEIRAS = (SHAREKEYS)
    ...
      SV = IV (0x12a5ce0) em 0x12a5cf0
      REFCNT = 1
      BANDEIRAS = (IOK, PIOK)
      IV = 1
---
SV = IV (0x12cb8b8) em 0x12cb8c8
  REFCNT = 1
  BANDEIRAS = (PADMY, ROK)
  RV = 0x12c26b0
  SV = PVHV (0x12aba00) em 0x12c26b0
    REFCNT = 1
    BANDEIRAS = (OBJETO, SHAREKEYS)
    STASH = 0x12d5300 "Classe"
    ...
      SV = IV (0x12c26b8) em 0x12c26c8
      REFCNT = 1
      BANDEIRAS = (IOK, PIOK)
      IV = 10

Com isso, sabe-se que 1) é um objeto 2) a qual pacote pertence, e isso informa seu uso.

Por exemplo, quando a desreferenciação dessa variável é encontrada ( $obj->name), um sub com esse nome é procurado no pacote (ou hierarquia), o objeto é passado como o primeiro argumento, etc.


1

I Seguindo esse pensamento para guiar o desenvolvimento Perl orientado a objetos.

Abençoe associar qualquer referência de estrutura de dados a uma classe. Dado como o Perl cria a estrutura de herança (em um tipo de árvore), é fácil tirar proveito do modelo de objeto para criar Objetos para composição.

Para essa associação que chamamos de objeto, desenvolver sempre tendo em mente que o estado interno do comportamento do objeto e da classe é separado. E você pode abençoar / permitir que qualquer referência de dados use qualquer comportamento de pacote / classe. Uma vez que o pacote pode entender "o estado emocional" do objeto.


Aqui estão os mesmos anúncios sobre como o Perl trabalha com namespaces de pacotes e como funciona com estados registrados no seu namespace. Porque existem pragmas como use namespace :: clean. Mas tente manter as coisas mais simples possíveis.
Steven Koch

-9

Por exemplo, se você tiver certeza de que qualquer objeto Bug será um hash abençoado, poderá (finalmente!) Preencher o código ausente no 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";
 }

Agora, sempre que o método print_me é chamado por meio de uma referência a qualquer hash que foi abençoado na classe Bug, a variável $ self extrai a referência que foi passada como o primeiro argumento e, em seguida, as instruções print acessam as várias entradas do hash abençoado.


@arch @ De qual fonte essa resposta foi plagiada?
Anderson Green
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.