Schwächen von Perl 5

Perl 5 ist eine tolle Programmiersprache, und ich benutze sie für die meisten Programme die ich schreibe. Doch bei aller Begeisterung komme ich nicht umhin ein paar Schwächen zu bemerken.

Dies soll keine FUD-Sammlung á la "Perl ist langsam, Perl Code ist unlesbar" sein, sondern soll einige Schwächen wohlbegründet darlegen.

Ansonsten bleibt mir noch auf Jonathan Rockways Blog zu verweisen, der schreibt warum er bei Perl bleibt, obwohl er dessen Nachteile kennt (Englisch), dem kann ich nur zustimmen.

Zu viele globale Variablen

In Perl Version 1 gab es nur globale Variablen, später kamen dann Variablen mit dynamischem Scope (local) und lexikalische Variablen (my) dazu.

Viele der eingebauten, speziellen Variablen wie $_, $! (Fehlermeldungen), $@ (eval-Fehler) und $/ (Input Record Separator) sind jedoch globale Variablen geblieben.

Das kann zu subtilen, sehr schwer zu findenden Bugs führen. Zum Beispiel führen Objekte, wenn sie nirgendwo mehr referenziert sind, ihre DESTROY-Methode aus, und dabei kann aus Versehen eine Fehlervariable zurückgesetzt werden:

use strict;
use warnings;

sub DESTROY { eval { 1 }; }

eval {
    my $x = bless {};
    die "Death\n";
};
print $@ if $@; # keine Ausgabe hier

Obwohl in dem eval-Block einmal die ausgeführt wurde, gibt die folgende Zeile nichts aus. Das liegt daran, dass das eval in der DESTROY-Methode keinen Fehler produziert hat und damit $@ zurückgesetzt wurde.

Im allgemeinen können viele Objekte aus dem Scope gehen, und wenn es ein Objekt aus einem fremden Modul ist, fällt es sehr schwer herauszufinden was passiert ist.

Die korrekte Lösung wäre, local $@ im DESTROY-Block zu verwenden. Das Problem ist, dass die wenigsten Entwickler an so etwas denken, und eigentlich sollten sie auch gar nicht daran denken müssen. Ein besseres Scoping der eingebauten Variablen könnte dieses Problem vermeiden.

Ein weiteres Problem der globalen Variablen ist, dass man als Modul-Autor genau wissen muss, welche Variablen man implizit verwendet. So benutzt zum Beispiel print implizit $\, and bei mehr als einem Argument auch noch $,. Wenn man ein Array in einen String interpoliert benutzt man implizit $".

Als Modul-Autor muss man alle diese Variablen localisieren um immer das Ergebnis zu bekommen das man will, auch wenn der Benutzer diese Variablen vorher auf andere Werte gesetzt hat.

Das impliziert auch, dass man beim Schreiben der Module sehr aufmerksam sein muss und sich beliebige Listen von Variablen merken muss.

Objektorientierung

Perls Objektsystem ist recht allgemein und flexibel (auch wenn es nicht immer so wahrgenommen wird), aber es hat auch einige Probleme.

So kennt perl keinen Unterschied zwischen Subroutinen und Methoden, was es quasi unmöglich macht eine Liste aller Methoden zu ermitteln, die man auf einem Objekt aufrufen kann. (Ausser man benutzt ein extra Objektframework wie Moose).

Auch wünscht man sich heute eine kürzere Syntax für Methodenaufrufe, -> ist suboptimal im Vergleich zu einem einzelnen Zeichen.

Regexes

Reguläre Ausdrücke, kurz Regexes, gehörten traditionell zu Perls Stärken.

Allerdings gibt es auch hier einige Schwächen. So sollten verschiedene Dinge verschieden aussehen, was jedoch nicht der Fall ist:

    (?:  )      # non-capturing group
    (?=  )      # positive look-ahead
    (?!  )      # negative look-ahead
    (?<= )      # positive look-behind
    (?<! )      # negative look-behind
    (?>  )      # non-backtracking pattern

Diese Gruppierungen machen zum Teil fundamental verschiedene Dinge, sehen aber sehr ähnlich aus, und sind relativ schwer zu lesen.

Auch wird die (?: ... )-Gruppe relativ häufig gebraucht, und dafür recht lange und schwer zu tippen.

Der Zugriff auf die Matchvariablen $1, $2, $3, ... ist nicht ohne Umwege über mit Array-Semantik möglich (auch wenn man sich mit @+, @- und substr so etwas basteln kann), und es ist nicht möglich, die Nummern der capturing groups dynamisch in der Regex festzulegen.

Die ungewöhnliche Handhabung von Unicode-Strings erschwert die Arbeit an der Regex-Engine und führt regelmäßig zu Bugs.

Unicode-Handling

Wenn man das Unicode-Modell von perl verstanden hat ist es dafür, dass es erst im Nachhinein in die Sprache integriert wurde sehr gut. Es versucht, die Art der internen Speicherung zu verbergen (perl speichert strings intern als Latin-1 oder UTF-8 ab, je nach Kontext).

Allerdings wendet perl auf intern als Latin-1 gespeicherte Strings ASCII-Semantik und nicht Unicode-Semantik an. Das bedeutet, dass sogar Strings, die durch Encode::decode gejagt wurden, nicht korrekt als Textstrings behandelt werden.

Um das zu umgehen, muss man den String mit utf8::upgrade zum UTF-8-String befördern - was entgegen der Philosophie läuft, das interne Speicherformat als Implementierungsdetail vor dem Programmierer zu verbergen.

Es würde auch sehr helfen, getrennte Datentypen für Text- und Bytestrings zu haben, sodass man z.B. beim Konkatenieren von verschiedenartigen Strings eine Warnung ausgeben könnte.

Spooky Action at a Distance

... nennt man Programmteile, die sich gegenseitig beeinflussen, obwohl man es nicht erwartet.

Ein Beispiel sind die impliziten Iteratoren über Hashes, die each benutzt. So führt der folgende Code zu einer Endlosschleife:

#!/usr/bin/perl 
use strict;
use warnings;

my %h = (a => 1, b => 2, c => 3);

while (my ($k, $v) = each %h){
    find($v);
}

sub find {
    while (my ($k, $v) = each %h){
        if ($v eq $_[0]){
            print "Found $v wth key $k\n";
            return;
        }
    }
}

Dadurch, dass der Iterator an den Hash gebunden ist, benutzen beide Programmteile den gleichen Iterator. D.h. nach dem lesen des ersten Paares wird find() aufgerufen, was den Rest des Hashes aufbraucht (anstatt von Vorne anzufangen), kehrt zurück, und die äußere while-Schleife fängt wieder von vorne an, weil der Iterator ja einmal ganz durchgelaufen war.