Why Perl sucks?
Возможно, "sucks" это слишком грубое слово, но по аналогии с "Why C sucks" и "Why C++ sucks" это, вероятно, подходящий заголовок.
Во-первых, разрешите мне сказать что Perl на данный момент мой любимый язык программирования. Я люблю его мощь, я люблю его элегантность, и, больше всего, я люблю его выразительность. Тем не менее, Perl, безусловно, не без недостатков.
Тон этой статьи не задуман как негативный. Я думаю нам будет полезно знать о слабых местах используемых языков программирования, особенно популярных, чтобы мы были уверены что эти слабости не попадут в другие языки.
Итак, вот мой список проблем в Perl:
Нет наследования объектов.
Наследование в Perl реализовано через массив @ISA (произносится как "is a", как в "this is a problem"), который разрешает вам вызывать методы из одного пакета через ссылку bless-нутую в другой пакет. Например, если SomeClass содержит SomeOtherClass в его глобальном массиве @ISA, то вы можете вызвать любой метод SomeOtherClass напрямую через ссылку bless-нутую в SomeClass.
К несчастью, не существует настоящего способа унаследовать объекты из другого класса в Perl, можно наследовать только их методы. Обычно используемый workaround - из конструктора класса, который хочет наследовать, вызывается конструктор класса, который наследуется, чтобы получить объект, добавить свои собственные поля в хеш, и потом пере-bless-нуть объект в ваш класс:
package SomeClass; use SomeOtherClass; @ISA = 'SomeOtherClass'; sub new { my $class = shift; # create a new SomeOtherClass object: my $self = $class->SUPER::new; # mess with it: $self->{'_something'} = 1; $self->{'_something_else'} = 2; # now bless it into SomeClass: bless($self, $class); return $self; } 1;Проблема заключается в том, что это нарушает законы инкапсуляции и абстрагирования: вам требуется знать детали реализации объекта, который вы наследуете, и потом вы влезаете прямо внутрь и работаете с его полями напрямую. Вы можете объявлять глобальные переменные в одном пакете, писать процедуры для манипулирования этими переменными, потом наследовать эти процедуры... но это всё не то.
С учётом бесчисленного количества объектно-ориентированных модулей на CPAN легко забыть, что Perl был изначально спроектирован как чисто структурный язык программирования. Такие моменты делают абсолютно ясным: ОО в Perl это на самом деле не более чем bless-нутые ссылки и немного синтаксического сахара.
Счётчик ссылок.
Perl использует счётчик ссылок для сборщика мусора, простой способ управления памятью, который гарантирует своевременное освобождение неиспользуемых ресурсов. Каждый скаляр, массив, хеш, что-либо ещё, имеет встроенный счётчик ссылок, который начинается с единицы. Каждый раз, когда на эту переменную создаётся ссылка, счётчик ссылок увеличивается. Когда ссылка удаляется, счётчик уменьшается. В какой-то момент, когда последняя ссылка удаляется, и счётчик достигает нуля, память используемая под эту переменную освобождается.
Проблема возникает, когда у вас есть две переменные ссылающиеся друг на друга, вроде родительского хеша и хеша ребёнка, которые обе содержат ссылки друг на друга. Поскольку ничей счётчик не может быть уменьшен до нуля пока последняя ссылка не уничтожена, один не может быть освобождён пока не освобождён второй, и в результате они оба остаются висеть в памяти. Это то, что известно как "циклические ссылки" (смотрите дополнительную информацию: http://www.perl.com/pub/a/2002/08/07/proxyobject.html ).
Это означает что устроить утечки памяти на Perl неожиданно просто. Многие подающие надежды Perl хакеры делали это случайно. Они думали: "Ха, было бы полезно чтобы этот объект содержал ссылку на родителя, и наоборот", и, вуаля, утечка памяти.
Perl, безусловно, не одинок в использовании счётчика ссылок: VB его использует, Python всё ещё его использует, как и PHP.
Вероятно величайшая проблема со счётчиками ссылок это дополнительная нагрузка на авторов XS-расширений. Счётчики заставляют их плодить вызовы SvREFCNT_inc() и SvREFCNT_dec() по всему коду, контролируя что каждый SvREFCNT_inc() имеет соответствующий SvREFCNT_dec(). Что приводит меня к следующему раздражению...
Не интуитивное API.
C API Perl довольно любопытное. Во-первых, отсутствует видимое соглашение об именах. Некоторые имена процедур записаны в смешанном регистре, например newSViv(), в то время как другие содержат подчёркивание, например newRV_noinc(). Многие имена переменных и членов структур имеют краткие, иногда дезориентирующие имена, например "cur" для длины и "len" для размера.
API также наводнено макросами, многие из которых не документированы в perlapi или любых других man страницах, например HvKEYS().
И, чтобы сделать жизнь ещё более интересной, для функций, которые документированы, зачастую не сказано, будут ли они изменять счётчики ссылок своих аргументов.
Не интуитивное поведение массивов/списков в скалярном контексте.
Мне на самом деле не нравится писать код с дополнительными скобками типа:
my ($first_field) = split(/\t/, $tab_delimited_fields);
чтобы помешать списку или массиву, возвращаемому из некоторой функции, повести себя неправильно.В Perl массивы возвращают свой размер, когда используются в скалярном контексте, а списки (напр. "(70, 80, 90)" в тексте программы) возвращают свой последний элемент. Во-первых, чего ради вообще введена разница между списками и массивами? Во-вторых, когда и зачем мне может потребоваться использовать список в скалярном контексте чтобы получить его последний элемент?
Я думаю было бы значительно лучше, если бы и массивы и списки работали одинаково и возвращали первый элемент в скалярном контексте, вместо их длины или последнего элемента. Тогда можно было бы писать код вроде:
my $email_address = $input =~ /(\S+\@\S+)/;
и он бы работал.Как же, вы спросите, тогда получить размер массива? Ну, почему бы не с помощью функции length()? Множество новичков предполагают что это должно работать именно так.
Форматы.
Форматы в Perl были предположительно "Report" частью из "Practical Extraction and Report Language" - конечно же "Perl" это уже не акроним, и когда, если честно, вы можете вспомнить что вы использовали форматы? На самом деле, можете ли вы вообще вспомнить как ими правильно пользоваться?
Эта большая, полностью игнорируемая часть Perl - отстой по разным причинам.
Во-первых, синтаксис определения форматов неуклюжий (как насчёт 20-ти или около того символов "<" один-за-другим?), глобальный (вам потребуется большой, заканчивающийся точкой блок где-то - вероятнее всего под вашим кодом) и абсолютно отличающийся от обычного синтаксиса Perl (увеличивая вероятность что вы его забудете, особенно поскольку вы никогда им не пользовались).
Во-вторых, попытка сделать что-нибудь полноценное с помощью форматов приводит к многословию, часто требует использования функции select() и возни с $^ перед вызовом write(), заставляя вас использовать три выражения для достижения того, для чего хватило бы и одного. (Четыре выражения, если считать и восстановление select().)
В-третьих, всё что умеют делать форматы, обычно умеют делать и printf() со sprintf()-ом. Даже когда синтаксис форматов действительно более гибок чем printf(), использование нескольких printf()-ов обычно позволяет решить проблему, и это более краткий и чистый способ.
Возможно худшая часть всего этого это то, что write() используется для вывода несуществующих форматов вместо выполнения I/O как read(), его английская противоположность:
"Note that write is *not* the opposite of 'read'. Unfortunately."
из (perldoc -f write).Нет констант или макросов.
На самом деле это должны быть два отдельных пункта, в Perl нет простого способа объявить переменные константами или определить макросы как альтернативу рассеивания магических чисел по всему коду.
Да, в Perl есть прагма "constant", которая должна делать и то, и другое, но это на самом деле просто хак; ловкий, элегантный хак, но тем не менее хак:
use constant PORT => 80;
PORT теперь может быть использован как rvalue в присвоениях, как если бы он был чем-то типа константы, и любая попытка присвоить что-то в него вызовет фатальную ошибку. Но, видите-ли, PORT на самом деле это не константа, это просто функция с прототипом "без аргументов". Вы всё ещё можете перекрыть её, переопределив функцию, или через "use sub", или напрямую через таблицу символов (symbol table). Что более важно, одна из целей констант это эффективность; сделать жизнь проще для компилятора избавляя его от необходимости присваивать что-то в переменную. Здесь же, вместо мелкого выигрыша, perl, интерпретатор, получает непотребный удар по производительности.
Нет информации о типе.
В Perl скаляры могут содержать либо значение, либо ссылку на значение. К сожалению, Perl не предоставляет операторов чтобы узнать какого типа "значение" содержит скаляр, оператор есть только для определения типа ссылок. Это приводит к тому, что, например, определить, содержит ли скаляр число подходящее для арифметических вычислений неожиданно сложно. Да, я понимаю что мы, Perl хакеры, привыкли думать, что числа и строки взаимозаменяемы, но проблема всё равно возникает, и нелепо обращаться за помощью к регулярным выражениям чтобы определить:
print "Not a number!" unless ($thing =~ /^\d+$/);
Конечно, это выражение не очень хорошо работает, так как числа могут содержать другие символы типа "+", "-" или "." для указания знака, дробной части или экспоненты.
Даже функции из ctype.h были бы более полезны, чем ничего.
Это ещё сильнее раздражает, потому что Perl, судя по всему, имеет довольно хорошее предположение, какого типа значение в SV (С-шный typedef описывающий значение скаляра) на основании значения поля "flags", и может легко конвертировать одно в другое при необходимости.
Autovivification.
Пары ключ/значение хешей autovivify (прим. переводчика: не знаю, как одним словом перевести autovivify, по смыслу - "создаются автоматически, на лету") в Perl. Это означает что вы можете указать новый ключ типа "autovived" и он возникнет:
my %hash = (key1 => 'value1', key2 => 'value2'); $hash{autovived} = 1;Это так же означает что вы можете опечататься в имени ключа и он тоже возникнет, без какого либо предупреждения. Это не является большой проблемой, когда вы используете хеши как словари, потому что ваши ключи часто приходят откуда-то ещё, но когда вы используете хеш как структуру или объект, это приводит к проблемам:
$self = { name => undef, age => undef ... sub name { my $self = shift; $self->{Name} = shift if @_; return $self->{name}; }Когда я использую хеши как объекты, я всегда определяю и инициализирую каждый элемент хеша внутри конструктора чем-то, как минимум "undef". Что только я бы не дал за ключевое слово "static", которое мешало бы новым элементам добавляться в хеш после инициализации:
static my $self = { name => undef, age => undef ... $self->{Name} = shift; # fatal error
Другие, более слабые раздражители, которые приходят на ум, включают отсутствие эквивалента для chop() перед строкой, невозможность применять файловые тесты -x по цепочке (вам требуется писать "-e $filename && -T $filename && -w $filename" вместо "-e -T -w $filename" или "-eTw $filename"), странное соглашение для вызова binmode(), нестабильные сигналы, дезориентирующие имена типа local(), и отсутствие функции sizeof().
Этот 16-летний язык неуклонно разбухает уже некоторое время. Многие его аспекты становятся не интуитивными, не эффективными, или просто уродливыми. Реализация сложно поддаётся изменениям, и добавление новых возможностей без поломки старых становится чрезвычайно сложной.
Perl 6 это попытка Perl коммьюнити исправить многие из этих проблем. Будет добавлена настоящая поддержка объектов, счётчик ссылок уйдёт, сигилы ($, @, %) будут более интуитивные, и множество новых возможностей будет добавлено.
Это переписывание языка сверху-вниз всё ещё далеко от завершения, но я, вместе со многими другими, жадно жду его. До этого момента, я думаю, я продолжу использовать Perl 5 для выполнения большей части моей работы.


Например, для определения типа скаляра есть функция looks_like_number() в модуле Scalar::Util. Ну и так далее, разных workaround-ов для описанных проблем существует некоторое кол-во.
Но сил на это уже нет. Два часа переводил, пол часа форматировал, а выдохся так, будто вагон разгружал. Вероятно, это связано с тем, что это мой первый перевод - до этого момента я предпочитал писать сам, а не переводить. Но эта статья очень понравилась, давно сам такое хотел написать. И, в общем и целом, в ней всё верно... хотя попридираться можно, если бы силы остались. :)
1. Такой разброс в названии функций, как ме кажется связан с тем что php разрабатывают/дорабатывают много людей, и если бы сейчас хотели бы упорядочить названия функций, было бы довольно сложно это сделать
2. Количество функций. Мне кажется, что именно поэтому php и популярен. Из за огромного количества функций которые могут выполнять рутинную работу. А то что они все сразу доступны. Опять же связано с архитектурой php
3. Насчет пространства имен, автор прав. Но к сожаленью например я, уже давно привык к этому =)
5. Бейте меня, но мне очень нравится работа с массивами в php. А для сравнения типов есть ===. Или при проверке, просто приводите переменную которую сравниваете к нужному типу.
6. Не хочу комментировать, так как сам начинаю задумывать насчет серьезности php
Хочу сказать что это всё, мое личное субьективное мнение. Я не гуру программинга на php и возможно что то говорю не правильно.
А питон вообще змея :D
str =~ /\d+/;
нагляднее, чем
re.search('\d+', str)
хотя может это просто привычка
Но, если к C++ добавить boost, то всё становится примерно так же удобно, просто многословнее.
Вот тут из двух крупных функций на Паскале получаются такие строки:
sub ckinn($) { $_[0]!~/^(\d{9})(\d)(?:(\d)(\d))?$/ || (($3) ?
$3!= c("0".$1.$2) || $4!= c($1.$2.$3): $2!= c("00".$1)) + 0 }
sub c($) { $a=shift; $c=0; map { $c+=chop($a)*$_ } (8,6,4,9,5,3,10,4,2,7,3); $c %11 %10 }
Это, конечно, просто зарядка для ума и развлечение, но возможности демонстрирует.
Хотя для тех кто пользует perl ежедневно пару лет и уже думает на нем, это наверное и не так.
P.S. Я ни в коем случае не умоляю достоинств регулярных выражений, даже напротив время от времени ломаю об них мозг :)
когда delphi-с-программисту приходится быстро переходить на web-порграммирование, причём нет полгода на обучение, а проект нужно сдать через месяц
P.S. А аutovivification, по моему мнению, скорее плюс чем минус.
Но сейчас классы это, по сути, хэши. Отсюда и растёт желание убрать аutovivification для некоторых конкретных хэшей.
http://search.cpan.org/~kvail/Tie-Strict…
Ждём и верим что p6 всё-же будет.
.?pl), с cp1251 на utf8, используя для этого iconv.
На перле такие вот мелочи делаются в три строки и занимают 5 минут, ибо на CPAN всё есть, а «магия» многих операторов и встроенных функций позволяет оставлять пространства для маневров при вызове получившийся утилиты. Питон не даёт такой гибкости.
Не понимаю - зачем в скриптовом языке использовать ООП...
кстати, а про константы, чем ему не нравиться такой метод
*CONST = 'Hello World';
$CONST = 'Good buy World'; # вызовет ошибку! все нормально
Пробовал питон для второй задачи, но он у меня как-то не прижился. Стараюсь писать все перловые программы в C-стиле, иначе, действительно, потом хрен поймёшь, что же делает программа. Ну, а основное преимущество — это, конечно, CPAN.
2. Со счетчиком ссылок все просто: при необходимости используйте средства для создания weak references (ссылок, не инкрементящих счетчик). Стандартно - Scalar::Util::weaken().
5. "Здесь же, вместо мелкого выигрыша, perl, интерпретатор, получает непотребный удар по производительности." - это заблужение. Действительно, use constant создает функциии (сделано это, чтобы не вводить доп. сущностей в язык). Но: умный компилятор инлайнит функции, возвращающие константное значение (не важно, созданные ли через use constant или вручную), так что при операциях с ними работа идет непосредственно со значением, вызова функции не происходит. Кроме того, в качестве констант можно использовать readonly переменные.
6. Непонятно, зачем определять тип переменной вместо того, чтобы просто использовать ее в нужном контексте. Впрочем, если есть необходимость, можно напрямую обратиться к IV или строковому значению SV. Пример:
use B;
my $x = "string"; $x = 10;
my $xb = B::svref_2object(\$x);
printf "%s, %d",$xb->PVX,$xb->IV;
eval q{*O_NONBLOCK = \\}.O_NONBLOCK;) работают немного быстрее и обычных скаляров, и инлайнящихся функций! Об этом можно почитать в другом моём посте: Import constants as scalar instead of bareword sub.Вариант с readonly-переменными я тоже предлагал выше, ибо TMTOWTDI, но у него есть один очень серьезный недостаток: переменные, даже readonly, все-таки можно переопределить, причем случайно (что мешает мне обьявить my $O_NONBLOCK ?) - и компилятор/рантайм не выдаст при этом никаких сообщений! При переопределении же constant sub (не важно, через sub CONST {} или *CONST = sub) получим предупреждение: Constant subroutine main::CONST_NAME redefined (в случае sub CONST - еще на этапе компиляции).
Да, "компилятор не может оптимизировать то, чего нет на этапе компиляции". Но проблема в том, что когда я подгружаю через use разные модули, я не могу заранее знать, на каком этапе эти модули генерируют/экспортируют константы - на этапе компиляции или на этапе выполнения. В доке такие нюансы обычно не упоминают, да и задолбаешься по каждому отдельному модулю это уточнять. Так что проблема таки есть!
Что касается объявления "my $O_NONBLOCK", то тут два нюанса. Во-первых существует соглашение, что в верхнем регистре пишутся только константы, так что "my $O_NONBLOCK" в середине кода - это красный флаг на который среагируют все квалифицированные программисты, и никто из них такого не напишет (если не хочет действительно переопределить эту константу, как грязный но нужный хак). А во-вторых никто не мешает через
no warnings 'redefine'отключить предупреждение о переопределении константной процедуры и переопределить её так же тихо, как и скаляр через "my".Но перл - это всего лишь инструмент. Всё остальное - вопросы мастерства и дисциплины.
Можно сказать, что самурайски меч - это слишком неудобный дивайс, ибо его нельзя метнуть в голову врагу. И ещё он шибко острый, об него обрезаться можно.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии. Авторизуйтесь, пожалуйста, или зарегистрируйтесь, если не зарегистрированы.