Blog

Auf dem Weg zu Perl 5.36 – Änderungen mit "use v5.36"

06.05.2022 // Renée Bäcker

Mittels use v5.<version> können Features und Standardeinstellungen für die angegebene Perl-Version geladen werden. Und es ist die minimal notwendige Perl-Version. Ein use v5.10 verlangt, dass das Programm mindestens mit Perl 5.10 ausgeführt wird. Darüber hinaus wird zum Beispiel das Feature say aktiviert.

Mit use v5.36 ändern sich einige Dinge, die in den folgenden Absätzen beschrieben werden.

warnings wird aktiviert

Es ist seit vielen Jahren bewährte Praxis, dass zu den ersten Zeilen eines jeden Programms oder Moduls die Zeilen use strict; und use warnings; gehören sollen. Mit use v5.36 ist das nicht mehr notwendig, denn damit wird beides automatisch aktiviert (strict wird schon seit längerem auf diese Weise aktiviert).

use v5.35;
 
my $s = 'test';
$s += 1;

Gibt diese Warnung aus:

Argument "test" isn't numeric in addition (+)

Einige Konstrukte werden zu echten Fehlern

Es gibt Dinge, die in Perl möglich sind aber schon seit vielen Jahren nicht mehr empfohlen werden. Drei dieser Dinge werden jetzt zu echten Fehlern. Ohne das use v5.36 laufen die noch, aber von der Verwendung rate ich ab.

Wer auch mit use v5.36 diese Konstrukte verwenden möchte, muss das explizit über use feature "feature_name" tun.

Multidimensional Hashes

Ich muss gestehen, dass ich diese Konstrukt noch nie in produktivem Code gesehen geschweige denn verwendet habe.

my %x;

$x{1,'talk'} = 'Perl Features';
print $x{1,'talk'};

Wie man sieht, ist hier der Hash kein einzelner Wert sondern eine Liste an Werten. Mit use v5.36 ist das nicht mehr erlaubt und die Fehlermeldung

Multidimensional hash lookup is disabled

erscheint.

Für diejenigen, die es interessiert: Bei diesen multidimensionalen Hashes werden die Werte der Liste zu einem Schlüssel zusammengefügt, so dass es ein normaler Hash ist. Die Werte werden dabei mit \x1C (File Separator) konkateniert, so dass man theoretisch auf den oben gezeigten Hasheintrag auch über

print $x{"1\x1Ctalk"};

zugreifen kann.

Indirekte Methodenaufrufe

Diese Schreibweise dürfte Programmierern mit Erfahrung in anderen Programmiersprachen bekannt vorkommen:

my $object = new Klasse;

Eigentlich sollte

my $object = Klasse->new;

verwendet werden.

Die erste Schreibweise nennt man indirekte Methodenaufrufe. Der Code kann so funktionieren wie es gewünscht ist, muss aber nicht. Das Problem wird vielleicht mit etwas Code deutlicher:

use v5.10;

package Klasse {
  sub new  { return bless {}, shift }
  sub test { return "test" }
};

sub hallo { return "hallo" };

my $object = new Klasse;
say $object;

say hallo $object;

my $test = test $object;
say $test;

say test $object;

Was passiert hier ab der Zeile 11?

In den Zeilen davor wird nur etwas Vorbereitung betrieben. Zeile 1 wird benötigt, damit das say zur Verfügung steht. Und in den Zeilen 3 bis 6 wird ein package definiert, das die zwei Subs new und test bereitstellt. Hier ist das package eine Klasse. In Zeile 8 wird noch eine Subroutine hallo definiert, die zum Hauptprogramm gehört.

Schon Gedanken dazu gemacht, was in den restlichen Zeilen des Codes passiert? Die Ausgabe sieht so aus:

Klasse=HASH(0x55e4b7c81340)
hallo
test
say() on unopened filehandle test at ...

In Zeile 10 wird die Funktion new des Pakets Klasse aufgerufen. Diese gibt ein Objekt zurück. Die Zeile 1 der Ausgabe bestätigt das. Funktioniert.

In Zeile 13 wird die Funktion hallo des Hauptprogramms ausgeführt und diese bekommt das Objekt als ersten Parameter übergeben (das sieht man jetzt nicht an der Ausgabe, kann aber durch eine kleine Änderung am Code selbst überprüft werden). Funktioniert.

In Zeile 15 wird die Methode test des Objekts aufgerufen, das Ergebnis (test) landet in der Variable $test und diese wird in Zeile 16 ausgegeben. Funktioniert.

Zeile 18 provoziert einen Fehler. Offensichtlich denkt perl, dass test ein Dateihandle ist (siehe auch nächstes Konstrukt), und nicht die Methode test des Objekts. Das heißt, ich muss bei dieser Schreibweise aufpassen, in welchem Umfeld ich die Methode aufrufen will.

Und was passiert wenn in Zeile 9 noch eine Subroutine test (sub test { "test2"} ) eingefügt wird? In diesem Fall wird perl diese Funktion in den Zeilen 15 und 18 genau diese Funktion ausführen und die Ausgabe wäre

Klasse=HASH(0x55e4b7c81340)
hallo
test2
test2

Es gibt noch mehr Fallen. Aus diesem Grund sind diese indirekten Methodenaufrufe mit use v5.36 nicht mehr erlaubt. Allerdings kann perl nur die indirekten Methodenaufrufe wie in Zeile 10 erkennen und es kommt zu der Fehlermeldung

Bareword "Klasse" not allowed while "strict subs" in use

Die Probleme oben verdeutlichen aber, warum grundsätzlich die Schreibweise mit -> verwendet werden soll. Denn damit weiß der Interpreter immer, welche Funktion/Methode ausgeführt werden muss.

Bareword Dateihandles

In vielen Perl-Tutorials, die so im Internet zu finden sind, wird das Schreiben einer Datei mit folgendem Code (oder so ähnlich) gezeigt:

open FH, ">/tmp/datei.txt" or die $!;
print FH "Eine Zeile\n";
close FH;

Dieses FH ist ein sogenanntes Bareword Filehandle. Der Name kann frei gewählt werden. Für einen Einstieg in Perl oder in Einzeilern ist die Verwendung dieser Bareword Filehandles auch ganz gut. Aber wer etwas Erfahrung mit Perl gesammelt hat, sollte darauf verzichten und zu lexikalischen Dateihandles übergehen:

open my $fh, ">", "/tmp/datei.txt" or die $!;
print $fh "Eine Zeile\n";
close $fh;

Ein Vorteil dieser lexikalischen Dateihandles haben wir beim vorherigen Konstrukt gezeigt. Was wenn es zufällig eine Subroutine mit dem Namen des *Bareword*s gibt? Es gibt aber noch weitere Vorteile: Die lexikalischen Dateihandles werden automatisch geschlossen, wenn sie den Gültigkeitsbereich verlassen (auch wenn man schon allein wegen der Fehlerbehandlung selbst das close machen sollte). Und sie sind eben lexikalisch, also nicht global.

Die Bareword Filehandles sind global und können so zu Seiteneffekten führen. Ein Beispiel:

use warnings;

read_data();

sub read_data {
    open FH, '</tmp/data.txt' or die $!;
    while ( my $line = <FH> ) {
        print $line;
        add_to_log( $line ) if $line =~ m{error};
    }
    close FH;
}

sub add_to_log {
    open FH, '>/tmp/log.txt' or die $!;
    print FH $_[0];
    close FH;
}

Wenn /tmp/data.txt eine Zeile mit error enthält, sieht die Ausgabe so aus:

ein
error
readline() on closed filehandle FH at ...

Das globale Filehandle wurde also in einer Subroutine geschlossen, bevor alle Zeilen der Datei in der Ursprungssubroutine gelesen werden konnten.

Benutzt man die Bareword Filehandles mit use v5.36, gibt es eine entsprechende Fehlermeldung:

Bareword filehandle "FH" not allowed under 'no feature "bareword_filehandles"' at ...

Einige Standard-*Bareword Filehandles* wie STDIN, STDOUT, STDERR und DATA werden auch mit use v5.36 funktionieren.


Permalink: