Чому прототипи функцій Perl 5 погані?


116

В іншому запитанні про переповнення стека Леон Тіммерманс стверджував:

Я б радив вам не використовувати прототипи. Вони мають своє використання, але не для більшості випадків і точно не в цьому.

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


Мені теж цікаво. Єдиний раз, коли я їх не використовую, це коли я телефоную зі змінною кількістю аргументів.
Пол Томблін

7
Чи можу я рекомендувати вам прочитати статтю "Прототипи Perl вважаються шкідливими" ?
tchrist

Відповіді:


121

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

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

  • Круглі дужки необов’язкові.
  • Контекст накладається на аргументи.

Наприклад, ви можете визначити функцію на зразок цієї:

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

і називати це як

mypush @array, 1, 2, 3;

не потребуючи написання, \щоб взяти посилання на масив.

Коротше кажучи, прототипи дозволяють вам створити свій власний синтаксичний цукор. Наприклад, фреймворк Moose використовує їх для імітації більш типового синтаксису OO.

Це дуже корисно, але прототипи дуже обмежені:

  • Вони повинні бути видимими під час компіляції.
  • Їх можна обійти.
  • Поширення контексту аргументами може спричинити несподівану поведінку.
  • Вони можуть ускладнити виклик функцій, використовуючи будь-яку іншу, крім строго встановленої форми.

Дивіться прототипи в perlsub для всіх деталей горі.


2
Я прийняв цю відповідь, тому що вважаю, що найкраще відповідає на питання - прототипи не є по суті поганими, це просто те, як ви їх використовуєте.
Альнітак

2
Прототипи Moose з іншого боку, / awesome / p3rl.org/MooseX::Declare p3rl.org/MooseX::Method
Кент Фредрік


Значить, вони є неправильним, значить?
Пітер Мортенсен

69

Проблема полягає в тому, що прототипи функцій Perl не роблять те, що люди думають, що роблять. Їх мета - дозволити записувати функції, які будуть проаналізовані як вбудовані функції Perl.

Перш за все, виклики методів повністю ігнорують прототипи. Якщо ви займаєтесь OO програмуванням, не має значення, яким прототипом є ваші методи. (Тому вони не повинні мати жодного прототипу.)

По-друге, прототипи не застосовуються суворо. Якщо ви викликаєте підпрограму з &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.


Але вони все ще забезпечують корисну перевірку того, що ваш абонент та підрозділ використовують однакову кількість аргументів, і що з цим погано?
Пол Томблін

2
Немає; загальна думка полягає в тому, що прототипи функцій Perl практично не приносять користі. Ви можете також не заважати їм, принаймні в Perl 5. Perl 6 може бути іншою (кращою) історією.
Джонатан Леффлер

5
Є кращі способи перевірки аргументів, наприклад, Params :: Validate module: search.cpan.org/~drolsky/Params-Validate-0.91/lib/Params/…
friedo

10
Виправлення: нарізка масиву повертає список , тому фрагмент масиву у скалярному контексті повертає завершальний елемент списку. Ваша виклик другого до останнього foo()друкує 2, тому що це заключний елемент у вашому фрагменті двох елементів. Перейдіть на, my @array = qw(foo bar baz)і ви побачите різницю. (Окрім цього, саме тому я не ініціалізую масиви / списки до числових послідовностей на основі 0 або 1 у викиданні, демонстративному коді. Плутанина між індексами, підрахунками та елементами у контекстах мене не раз укусила. Дурний, але правдивий.)
стовп

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

Ця функція, можливо, ви хочете, якщо ви розглядаєте прототипи.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.