Что именно делает «благословение» Perl?


143

Насколько я понимаю, в Perl используется ключевое слово "bless" внутри метода "new" класса:

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

Но что именно «bless» делает с этой хеш-ссылкой?


2
См. "Bless My Referents" 1999 года. Выглядит довольно подробно. (К сожалению, в руководстве по Perl мало что можно сказать об этом.)
Джон Скит,

Ответы:


144

Обычно blessсвязывает объект с классом.

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

Теперь, когда вы вызываете метод $object, Perl знает, в каком пакете искать метод.

Если второй аргумент опущен, как в вашем примере, используется текущий пакет / класс.

Для ясности ваш пример можно записать следующим образом:

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

РЕДАКТИРОВАТЬ: см. Хороший ответ kixx для получения более подробной информации.


79

bless связывает ссылку с пакетом.

Не имеет значения, на что идет ссылка, это может быть хэш (наиболее распространенный случай), массив (не так часто), скаляр (обычно это указывает на объект наизнанку ), регулярное выражение , подпрограмма или TYPEGLOB ( полезные примеры см. в книге Дамиана Конвея «Объектно-ориентированный Perl: всеобъемлющее руководство по концепциям и методам программирования» ) или даже ссылку на дескриптор файла или каталога (наименее распространенный случай).

Эффект bless-ing заключается в том, что он позволяет применять специальный синтаксис к благословенной ссылке.

Например, если благословенная ссылка хранится в $obj(связанная blessс пакетом «Класс»), то $obj->foo(@args)вызовет подпрограмму fooи передаст в качестве первого аргумента ссылку, $objза которой следуют остальные аргументы ( @args). Подпрограмма должна быть определена в пакете «Класс». Если fooв пакете «Class» подпрограммы нет, @ISAбудет выполнен поиск в списке других пакетов (взятых из массива в пакете «Class») и fooбудет вызвана первая найденная подпрограмма .


16
Ваше первоначальное утверждение неверно. Да, bless принимает ссылку в качестве первого аргумента, но благословляется референтная переменная, а не сама ссылка. $ perl -le 'sub Somepackage :: foo {42}; % h = (); $ h = \% h; благослови $ h, "Somepackage"; $ j = \% h; print $ j-> UNIVERSAL :: can ("foo") -> () '42
converter42

1
Объяснение Kixx исчерпывающее. Не стоит возиться с подбором преобразователя по теоретическим мелочам.
Blessed Geek

19
@Blessed Geek, Это не теоретические мелочи. Разница имеет практическое применение.
ikegami

3
Старая ссылка perlfoundation.org для "объекта наизнанку" теперь в лучшем случае находится за стеной входа в систему. Ссылка на оригинал Archive.org находится здесь .
ruffin

2
Возможно, это будет заменять неработающую ссылку, которую прокомментировал @harmic
Rhubbarb

9

Краткая версия: он отмечает этот хэш как прикрепленный к текущему пространству имен пакета (чтобы этот пакет предоставлял реализацию своего класса).


7

Эта функция сообщает объекту, на который ссылается REF, что теперь это объект в пакете CLASSNAME или текущий пакет, если CLASSNAME опущен. Рекомендуется использовать форму bless с двумя аргументами.

Пример :

bless REF, CLASSNAME
bless REF

Возвращаемое значение

Эта функция возвращает ссылку на объект, благословленный в CLASSNAME.

Пример :

Ниже приведен пример кода, показывающий его основное использование, ссылка на объект создается путем благословения ссылки на класс пакета.

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

Я дам ответ здесь, так как те, что здесь, мне не понравились.

Функция bless в Perl связывает любую ссылку со всеми функциями внутри пакета.

Зачем нам это нужно?

Начнем с выражения примера на 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
})();

Теперь давайте удалим конструкцию класса и обойдемся без нее:

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

Функция принимает хэш-таблицу неупорядоченных свойств (поскольку в 2016 году нет смысла писать свойства в определенном порядке на динамических языках) и возвращает хеш-таблицу с этими свойствами, или, если вы забыли ввести ключевое слово new, она вернет весь глобальный контекст (например, окно в браузере или глобальный в nodejs).

Perl не имеет «этого», «нового» или «класса», но все же может иметь функцию, которая ведет себя аналогичным образом. У нас не будет ни конструктора, ни прототипа, но мы сможем создавать новых животных по желанию и изменять их индивидуальные свойства.

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

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

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

Теперь у нас есть проблема: что, если мы хотим, чтобы животное издавало звуки само по себе, а не мы печатали его голос. То есть нам нужна функция performSound, которая печатает собственный звук животного.

Один из способов добиться этого - научить каждое отдельное Животное издавать звук. Это означает, что у каждой кошки есть собственная дублирующая функция для выполнения звука.

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

Это плохо, потому что performSound помещается как совершенно новый объект функции каждый раз, когда создается животное. 10000 животных означает 10000 звуков. Мы хотим, чтобы у всех животных была одна функция performSound, которая ищет свой звук и печатает его.

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

На этом параллель с Perl вроде бы заканчивается.

Оператор new в JavaScript не является обязательным, без него "this" внутри методов объекта искажает глобальную область видимости:

(() => {
    // '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.

})();

Мы хотим иметь одну функцию для каждого Animal, которая будет искать собственный звук этого животного, а не жестко кодировать его при создании.

Благословение позволяет нам использовать пакет в качестве прототипа объектов. Таким образом, объект знает о «пакете», на который он «ссылается», и, в свою очередь, может иметь функции в пакете «достигать» конкретных экземпляров, которые были созданы из конструктора этого «объекта пакета»:

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

Резюме / TL; DR :

В Perl нет «этого», «класса» или «нового». благословение объекта на пакет дает этому объекту ссылку на пакет, и когда он вызывает функции в пакете, их аргументы будут смещены на 1 слот, а первый аргумент ($ _ [0] или сдвиг) будет эквивалентен javascript "это". В свою очередь, вы можете имитировать модель прототипа JavaScript.

К сожалению, это делает невозможным (насколько я понимаю) создание «новых классов» во время выполнения, так как вам нужно, чтобы каждый «класс» имел свой собственный пакет, тогда как в javascript вам не нужны пакеты вообще, как ключевое слово «new» составляет анонимную хэш-карту для использования в качестве пакета во время выполнения, в который вы можете добавлять новые функции и удалять функции на лету.

Есть некоторые библиотеки Perl, которые создают свои собственные способы преодоления этого ограничения в выразительности, например Moose.

Почему неразбериха? :

Из-за пакетов. Наша интуиция подсказывает нам привязать объект к хэш-карте, содержащей его прототип. Это позволяет нам создавать «пакеты» во время выполнения, как это делает JavaScript. Perl не обладает такой гибкостью (по крайней мере, не встроен, вы должны изобретать его или получать из других модулей), и, в свою очередь, ваша выразительность во время выполнения затруднена. Назвать это «благословением» тоже не очень помогает.

Что мы хотим делать :

Что-то вроде этого, но имеют рекурсивную привязку к карте прототипа и неявно связаны с прототипом, а не должны делать это явно.

Вот наивная попытка сделать это: проблема в том, что «call» не знает, «что вызвало это», поэтому это также может быть универсальная функция perl «objectInvokeMethod (object, method)», которая проверяет, есть ли у объекта метод , или его прототип имеет это, или его прототип есть, пока он не достигнет конца и не найдет его или нет (прототипное наследование). В Perl для этого есть прекрасная магия eval, но я оставлю это на то, что я могу попробовать сделать позже.

В любом случае вот идея:

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

В любом случае, надеюсь, кто-нибудь сочтет этот пост полезным.


Во время выполнения невозможно создавать новые классы. my $o = bless {}, $anything;благословит объект в $anythingкласс. Аналогичным образом {no strict 'refs'; *{$anything . '::somesub'} = sub {my $self = shift; return $self->{count}++};будет создан метод с именем somesub в классе с именем in $anything. Все это возможно во время выполнения. «Возможно», однако, не делает его хорошей практикой для повседневного использования. Но это полезно при создании систем наложения объектов, таких как Moose или Moo.
DavidO

интересно, значит, вы говорите, что я могу благословить референт в класс, имя которого определяется во время выполнения. Выглядит интересно и отменяет мою unfortunately it makes it impossible(to my understanding) to create "new classes" at runtimeпретензию. Я предполагаю, что моя озабоченность в конечном итоге сводилась к тому, что это было значительно менее интуитивно понятным для управления / анализа системы пакетов во время выполнения, но до сих пор мне не удалось показать то, что она по своей сути не может сделать. Система пакетов, похоже, поддерживает все инструменты, необходимые для добавления / удаления / проверки / изменения себя во время выполнения.
Дмитрий

Это верно; вы можете управлять таблицей символов Perl программно и, следовательно, управлять пакетами Perl и членами пакета во время выполнения, даже не объявив где-либо "package Foo". Проверка таблицы символов и манипулирование ею во время выполнения, семантика AUTOLOAD, атрибуты подпрограмм, привязка переменных к классам ... Есть много способов проникнуть в суть. Некоторые из них полезны для автоматического создания API, инструментов проверки, API автодокументирования; мы не можем предсказать все варианты использования. Возможным исходом такой хитрости также является простреление себе ноги.
DavidO

4

Наряду с рядом хороших ответов, blessссылка на -ed особенно отличает то , что SV для нее добавляются дополнительные FLAGS( OBJECT) иSTASH

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'

Отпечатки с одними и теми же (и не имеющими отношения к этому) частями подавлены

SV = IV (0x12d5530) при 0x12d5540
  REFCNT = 1
  ФЛАГИ = (ROK)
  RV = 0x12a5a68
  SV = PVHV (0x12ab980) при 0x12a5a68
    REFCNT = 1
    ФЛАГИ = (КЛЮЧИ)
    ...
      SV = IV (0x12a5ce0) при 0x12a5cf0
      REFCNT = 1
      ФЛАГИ = (IOK, pIOK)
      IV = 1
---
SV = IV (0x12cb8b8) при 0x12cb8c8
  REFCNT = 1
  ФЛАГИ = (PADMY, ROK)
  RV = 0x12c26b0
  SV = PVHV (0x12aba00) при 0x12c26b0
    REFCNT = 1
    ФЛАГИ = (ОБЪЕКТ, КЛЮЧИ)
    STASH = 0x12d5300 "Класс"
    ...
      SV = IV (0x12c26b8) при 0x12c26c8
      REFCNT = 1
      ФЛАГИ = (IOK, pIOK)
      IV = 10

При этом известно, что 1) это объект 2) какому пакету он принадлежит, и это сообщает его использование.

Например, когда встречается разыменование этой переменной ( $obj->name), подпрограмма с таким именем ищется в пакете (или иерархии), объект передается в качестве первого аргумента и т. Д.


1

Следуя этой мысли, я направляю разработку объектно-ориентированного Perl.

Благослови связывать любую ссылку на структуру данных с классом. Учитывая, как Perl создает структуру наследования (в виде дерева), легко использовать преимущества объектной модели для создания объектов для композиции.

Мы назвали эту ассоциацию объектом, чтобы при разработке всегда иметь в виду, что внутреннее состояние объекта и поведение класса разделены. И вы можете благословить / разрешить любой ссылке на данные использовать любое поведение пакета / класса. С пакетом можно понять «эмоциональное» состояние объекта.


Здесь же рассказывается о том, как Perl работает с пространствами имен пакетов и как работает с состояниями, зарегистрированными в вашем пространстве имен. Поскольку существуют такие прагмы, как use namespace :: clean. Но постарайтесь, чтобы все было проще.
Стивен Кох

-9

Например, если вы уверены, что любой объект Bug будет хеш-кодом, вы можете (наконец!) Заполнить недостающий код в методе 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";
 }

Теперь, когда метод print_me вызывается через ссылку на любой хэш, добавленный в класс Bug, переменная $ self извлекает ссылку, которая была передана в качестве первого аргумента, а затем операторы печати обращаются к различным записям благословенного хеша.


@darch Из какого источника этот ответ был заимствован?
Андерсон Грин
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.