Почему прототипы функций Perl 5 плохие?

116

В другом вопросе о переполнении стека Леон Тиммерманс утверждал:

Я бы посоветовал вам не использовать прототипы. У них есть свое применение, но не в большинстве случаев и определенно не в этом.

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

Альнитак
источник
Мне тоже любопытно. Единственный раз, когда я их не использую, - это когда я звоню с переменным количеством аргументов.
Пол Томблин
7
Могу я порекомендовать вам прочитать статью «Прототипы Perl, признанные вредными» ?
tchrist

Ответы:

121

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

Прототипы позволяют вам определять функции, которые ведут себя как встроенные функции.

  • Скобки необязательны.
  • На аргументы накладывается контекст.

Например, вы можете определить такую ​​функцию:

sub mypush(\@@) { ... }

и назовите это как

mypush @array, 1, 2, 3;

без необходимости писать, \чтобы получить ссылку на массив.

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

Это очень полезно, но прототипов очень мало:

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

См. « Прототипы» в perlsub для всех подробностей.

Майкл Карман
источник
2
Я принял этот ответ, потому что считаю, что он лучше всего отвечает на вопрос - прототипы не плохи по своей сути, просто вы их используете.
Alnitak
2
С другой стороны, прототипы лося / awesome / p3rl.org/MooseX::Declare p3rl.org/MooseX::Method::Signatures
Kent Fredric
Значит, они неправильно употреблены?
Питер Мортенсен
69

Проблема в том, что прототипы функций Perl не делают того, что люди думают о них. Их цель - позволить вам писать функции, которые будут анализироваться, как встроенные функции Perl.

Во-первых, вызовы методов полностью игнорируют прототипы. Если вы занимаетесь объектно-ориентированным программированием, не имеет значения, какой прототип у ваших методов. (Значит, у них не должно быть прототипа.)

Во-вторых, прототипы строго не соблюдаются. Если вы вызываете подпрограмму с помощью &function(...), прототип игнорируется. Таким образом, они не обеспечивают никакой безопасности типов.

В-третьих, они жуткие действия на расстоянии. (Особенно $прототип, который заставляет соответствующий параметр оцениваться в скалярном контексте, а не в контексте списка по умолчанию.)

В частности, они затрудняют передачу параметров из массивов. Например:

my @array = qw(a b c);

foo(@array);
foo(@array[0..1]);
foo($array[0], $array[1], $array[2]);

sub foo ($;$$) { print "@_\n" }

foo(@array);
foo(@array[0..1]);
foo($array[0], $array[1], $array[2]);

печатает:

a b c
a b
a b c
3
b
a b c

вместе с 3 предупреждениями о main::foo() called too early to check prototype(если предупреждения включены). Проблема в том, что массив (или срез массива), вычисленный в скалярном контексте, возвращает длину массива.

Если вам нужно написать функцию, которая действует как встроенная, используйте прототип. В противном случае не используйте прототипы.

Примечание: Perl 6 будет иметь полностью переработанные и очень полезные прототипы. Этот ответ применим только к Perl 5.

CJM
источник
Но они по-прежнему обеспечивают полезную проверку того, что ваш вызывающий и подпрограмма используют одинаковое количество аргументов, так что в этом плохого?
Пол Томблин
2
Нет; По общему мнению, прототипы функций Perl практически не приносят пользы. Вы также можете не беспокоиться о них, по крайней мере, в Perl 5. Perl 6 может быть другой (лучшей) историей.
Джонатан Леффлер
5
Есть более эффективные способы проверки аргументов, такие как модуль Params :: Validate: search.cpan.org/~drolsky/Params-Validate-0.91/lib/Params/…
friedo
10
Исправление: нарезка массива возвращает список , поэтому фрагмент массива в скалярном контексте возвращает последний элемент списка. Ваш предпоследний вызов foo()printts 2, потому что это последний элемент в вашем двухэлементном срезе. Измените на, my @array = qw(foo bar baz)и вы увидите разницу. (Кстати, вот почему я не инициализирую массивы / списки числовыми последовательностями на основе 0 или 1 в одноразовом демонстрационном коде. Путаница между индексами, счетчиками и элементами в контекстах укусила меня не один раз. Глупо, но факт.)
pilcrow
2
@pilcrow: Я отредактировал ответ, чтобы a b cпрояснить вашу точку зрения.
Flimm
30

Я согласен с двумя вышеупомянутыми плакатами. В общем, $следует избегать использования. Прототипы полезны только при использовании блока аргументов ( &), шарики ( *), или ссылки на прототипы ( \@, \$, \%, \*)

Леон Тиммерманс
источник
В общем, возможно, но я хотел бы упомянуть два исключения: во-первых, ($)прототип создает именованный унарный оператор, который может быть полезен (конечно, Perl находит их полезными; я тоже иногда). Во-вторых, при переопределении встроенных модулей (будь то импорт или использование CORE :: GLOBAL: :), вы, как правило, должны придерживаться того прототипа, который был у встроенного модуля, даже если он включает $, иначе вы можете удивить программиста (вы сами, даже) с контекстом списка, где встроенный в противном случае предоставил бы скалярный контекст.
Сидекин
4

Некоторые люди, глядя на прототип подпрограммы Perl, думают, что это означает то, чего не делает:

sub some_sub ($$) { ... }

Для Perl это означает, что парсер ожидает два аргумента. Это способ Perl, позволяющий создавать подпрограммы, которые ведут себя как встроенные, и все они знают, чего ожидать от следующего кода. О прототипах можно прочитать в perlsub

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

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

use v5.20;
use feature qw(signatures);
no warnings qw(experimental::signatures);

animals( 'Buster', 'Nikki', 'Godzilla' );

sub animals ($cat, $dog, $lizard = 'Default reptile') { 
    say "The cat is $cat";
    say "The dog is $dog";
    say "The lizard is $lizard";
    }

Это функция, которую вы, вероятно, захотите, если вы рассматриваете прототипы.

Брайан Д Фой
источник