Pull to refresh

Why Perl sucks?

Reading time7 min
Views4.7K
Original author: William G. Davis
Возможно, «sucks» это слишком грубое слово, но по аналогии с
«Why C sucks»
и
«Why C++ sucks»
это, вероятно, подходящий заголовок.

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

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

Итак, вот мой список проблем в Perl:

  1. Нет наследования объектов.



    Наследование в 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 для выполнения большей части моей работы.
Tags:
Hubs:
+35
Comments78

Articles