Perl конструктор ключевое слово 'новое'

Я новичок в Perl и в настоящее время изучаю объектно-ориентированный Perl и столкнулся с написанием конструктора. Похоже, что при использовании new для имени подпрограммы первым параметром будет имя пакета.

Должен ли конструктор использовать ключевое слово new? Или это потому, что когда мы вызываем подпрограмму new, используя имя пакета, то первым передаваемым параметром будет имя пакета?

packagename->new;

а когда у подпрограммы другое имя, то первым параметром будет ссылка на объект? Или это потому, что когда подпрограмма вызывается через ссылку на объект, то первым передаваемым параметром будет ссылка на объект?

$objRef->subroutine;

person user2763829    schedule 08.06.2014    source источник
comment
Возможно, вам будет полезно прочитать «Modern Perl», если вы еще этого не сделали. modernperlbooks.com/books/modern_perl_2014   -  person Neil H Watson    schedule 08.06.2014
comment
В Perl нет ключевого слова new. Это обычное имя для конструктора объекта, и оно используется многими классами, но ни в коем случае не является обязательным.   -  person tchrist    schedule 08.06.2014
comment
Да, вы правы: invocant всегда является первым аргументом (или нулевым) при вызове метода, независимо от метода вызова.   -  person tchrist    schedule 08.06.2014


Ответы (2)


Это не первый параметр new, а косвенный синтаксис объекта,

perl -MO=Deparse -e 'my $o = new X 1, 2'

который анализируется как

my $o = 'X'->new(1, 2);

Из perldoc,

Perl поддерживает другой синтаксис вызова метода, называемый нотацией «косвенного объекта». Этот синтаксис называется косвенным, потому что метод предшествует объекту, для которого он вызывается.

При этом new — это не какое-то зарезервированное слово для вызова конструктора, а имя самого метода/конструктора, которое в Perl не применяется (т.е. DBI имеет connect конструктор)

person mpapec    schedule 08.06.2014
comment
поэтому первым параметром будет «X», который должен быть именем пакета для подпрограммы new, потому что новая подпрограмма вызывается с использованием «X»? - person user2763829; 08.06.2014
comment
@ user2763829 да, но обратите внимание, что только 1 и 2 являются параметрами. - person mpapec; 08.06.2014
comment
Вызываемая здесь функция, по модулю наследования, будет X::new("X", 1, 2) и, таким образом, получит инвокант в качестве своего нулевого аргумента. Кроме того, этот краткий обзор вызова метода косвенного объекта, вероятно, недостаточен для того, чтобы кто-нибудь понял, что это на самом деле означает. См. справочную страницу perglossary(1). - person tchrist; 08.06.2014
comment
Речь идет об объектах дательного падежа в английском языке: косвенный объект: в английской грамматике короткая именная группа между глаголом и его прямым объектом, указывающая на бенефициара. или получатель действия. В Perl print STDOUT "$foo\n"; можно понимать как «объект косвенного объекта глагола», где STDOUT — получатель действия печати, а $foo — печатаемый объект. Точно так же при вызове метода вы можете поместить вызывающее выражение в дательный падеж между методом и его аргументами: $gollum = new Pathetic::Creature "Sméagol"; give $gollum "Fisssssh!"; give $gollum "Precious!"; - person tchrist; 08.06.2014
comment
Слот косвенного объекта: синтаксическая позиция между вызовом метода и его аргументами при использовании синтаксиса косвенного вызова объекта. (Слот отличается отсутствием запятой между ним и следующим аргументом.) STDERR находится здесь в косвенном слоте объекта: print STDERR "Awake! Awake! Fear, Fire, Foes! Awake!\n"; - person tchrist; 08.06.2014
comment
@tchrist tnx за указание на параллели с естественным языком, и не стесняйтесь улучшать ответ. Лично я не сторонник непрямого синтаксиса, так как перл ОО сам по себе кажется досадным историческим артефактом. - person mpapec; 08.06.2014
comment
Я, конечно же, не собираюсь изо всех сил пытаться защищать вызов метода в дативном стиле (на основе позиции) в Perl. Однако у меня есть проблема с номенклатурой, потому что она противоречит традиционному именованию этих вещей. - person tchrist; 08.06.2014

Примечание. Все приведенные ниже примеры упрощены в учебных целях.

О методах

Да вы правы. Первым аргументом вашей функции new, если вызывается как метод, будет то, против чего вы ее вызвали.

Есть два «разновидности» вызова метода, но результат один и тот же в любом случае. Один вариант зависит от оператора, бинарного оператора ->. Другой вариант основан на упорядочении аргументов, подобно тому, как бипереходные глаголы работают в английском языке. Большинство людей используют дательный/двупереходный стиль только со встроенными и возможно с конструкторами, но редко с чем-либо еще.

В большинстве (но не во всех) обстоятельствах эти первые два эквивалентны:

<сильный>1. Дательный падеж Вызов методов

Это позиционный, тот, который использует порядок слов, чтобы определить, что происходит.

use Some::Package;
my $obj1 = new Some::Package NAME => "fred"; 

Обратите внимание, что мы не используем здесь стрелку метода: нет ->, как написано. Это то, что сам Perl использует со многими своими собственными функциями, такими как

 printf STDERR "%-20s: %5d\n", $name, $number;

Что почти все предпочитают эквиваленту:

 STDERR->printf("%-20s: %5d\n", $name, $number);

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

<сильный>2. Стрелка Вызов методов

Вызов стрелки по большей части более четкий и чистый, и с меньшей вероятностью запутает вас в сорняках Perl-анализа странностей. Обратите внимание, я сказал менее вероятно; Я не говорил, что он свободен от всех недостатков. Но давайте просто притворимся, что это так для целей этого ответа.

use Some::Package;
my $obj2 = Some::Package->new(NAME => "fred");

Во время выполнения, за исключением каких-либо причудливых странностей или вопросов наследования, фактический вызов функции будет

 Some::Package::new("Some::Package", "NAME", "fred");

Например, если бы вы работали в отладчике Perl и сделали дамп стека, в цепочке вызовов было бы что-то похожее на предыдущую строку.

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

package Some::Package;
sub new {
   my($classname, @arguments) = @_;
   my $obj = { @arguments };
   bless $obj, $classname;
   return $obj;
}

Это просто чрезвычайно упрощенный пример новых наиболее частых способов вызова конструкторов и того, что происходит внутри. В реальном производственном коде конструктор был бы намного осторожнее.

Методы и косвенность

Иногда вы не знаете имя класса или имя метода во время компиляции, поэтому вам нужно использовать переменную для хранения того или другого, или обоих. Косвенность в программировании отличается от косвенных объектов в естественном языке. Косвенность просто означает, что у вас есть переменная, которая содержит что-то еще, поэтому вы используете переменную, чтобы получить ее содержимое.

print 3.14;    # print a number directly

$var = 3.14;   # or indirectly
print $var;

Мы можем использовать переменные для хранения других вещей, связанных с вызовом метода, помимо аргументов метода.

<сильный>3. Вызов стрелки с косвенным именем метода:

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

use Some::Package;
my $action = (rand(2) < 1) ? "new" : "old";
my $obj    = Some::Package->$action(NAME => "fido");

Здесь имя самого метода неизвестно до времени выполнения.

<сильный>4. Вызов стрелки с косвенным именем класса:

Здесь мы используем переменную для хранения имени класса, который мы хотим использовать.

my $class = (rand(2) < 1) 
              ? "Fancy::Class" 
              : "Simple::Class";
my $obj3 = $class->new(NAME => "fred");

Теперь мы случайным образом выбираем тот или иной класс.

Вы также можете использовать дательный падеж таким образом:

my $obj3 = new $class NAME => "fred";

Но это обычно не делается с помощью пользовательских методов. Однако иногда это происходит со встроенными.

my $fh = ($error_count == 0) ? *STDOUT : *STDERR;
printf $fh "Error count: %d.\n", $error_count;

Это потому, что попытка использовать выражение в слоте дательного падежа вообще не будет работать без блока вокруг него; в противном случае это может быть только простая скалярная переменная, даже не один элемент из массива или хеша.

printf { ($error_count == 0) ? *STDOUT : *STDERR } "Error count: %d.\n", $error_count;

Или проще:

print { $fh{$filename} } "Some data.\n";

Что чертовски некрасиво.

Пусть инициатор остерегается

Обратите внимание, что это не работает идеально. Литерал в слоте дательного падежа работает иначе, чем переменная. Например, с буквальными файловыми дескрипторами:

print STDERR;

означает

print STDERR $_;

но если вы используете косвенные файловые дескрипторы, например:

print $fh;

Это на самом деле означает

print STDOUT $fh;

что вряд ли означает то, что вы хотели, что, вероятно, было так:

print $fh $_;

он же

$fh->print($_);

Продвинутое использование: Методы двойной природы

Особенность стрелки вызова метода -> заключается в том, что она не зависит от того, является ли ее левый операнд строкой, представляющей имя класса, или благословенной ссылкой, представляющей экземпляр объекта.

Конечно, ничто формально не требует, чтобы $class содержало имя пакета. Это может быть и то, и другое, и если это так, то сам метод должен поступить правильно.

use Some::Class;

my $class = "Some::Class";
my $obj   = $class->new(NAME => "Orlando"); 

my $invocant = (rand(2) < 1) ? $class : $obj;
$invocant->any_debug(1);

Для этого требуется довольно причудливый метод any_debug, который делает что-то другое в зависимости от того, благословлен его вызывающий объект или нет:

package Some::Class;
use Scalar::Util qw(blessed);

sub new {
   my($classname, @arguments) = @_; 
   my $obj = { @arguments };
   bless $obj, $classname;
   return $obj;
}   

sub any_debug {
    my($invocant, $value) = @_;
    if (blessed($invocant)) {
        $invocant->obj_debug($value);
    } else {
        $invocant->class_debug($value);
    }
}

sub obj_debug {
    my($self, $value) = @_;
    $self->{DEBUG} = $value;
}

my $Global_Debug;
sub class_debug {
    my($classname, $value) = @_;
    $Global_Debug = $value;
}

Однако это довольно продвинутая и тонкая техника, применимая лишь в нескольких необычных ситуациях. Это не рекомендуется для большинства ситуаций, так как может привести к путанице при неправильном обращении — и, возможно, даже если это так.

person tchrist    schedule 08.06.2014