Tobi’s Blog

Gedanken zur Softwareentwicklung und anderes

Archiv für die 'Perl::Kinky' Kategorie

Alles zum Thema *Würg*

Stolperfallen in Perl’s Spezialmethoden: DESTROY

Erstellt von Tobi am 15. Juli 2008

Durch einen Kollegen bin ich auf ein Problem aufmerksam geworden, was ein verwirrendes Verhalten von globalen Variablen in Verbindung mit DESTROY beschreibt. Man nehme folgenden Code und betrachte speziell den unteren eval-Block:

use Test::More tests => 1;
{
package Foo;
sub new {
bless([])
}
sub DESTROY {
eval {
die('destroy');
}
}
sub bar {
die('bar');
}
}
eval {
my $foo = Foo->new();
$foo->bar();
};
main::diag($@);

in $@ sollte eigentlich sowas wie bar at Test.pm line x erscheinen, aber er ergibt destroy at…. Was ist also passiert? Es ist, wie in der “Fehlerbeschreibung” erwähnt eigentlich kein Fehler, sondern eine Nebenwirkung davon, dass in Perl fast alles mit globalen Variablen gelöst wird. Wenn man den Code ein wenig erweitert, sieht man deutlich, was passiert:

#!perl -T
use Test::More tests => 1;
{
package Foo;
sub new {
main::diag('new() called');
bless([])
}
sub DESTROY {
main::diag('DESTROY() called');
eval {
die('destroy');
}
}
sub bar {
main::diag('bar() called');
die('bar');
}
}
main::diag('eval-START');
eval {
my $foo = Foo->new();
$foo->bar();
}; # HERE!
main::diag('eval-END');
like($@, qr/^destroy/, 'croak in DESTROY()');

Folgende Ausgabe wird erzeugt:
# eval-START
# new() called
# bar() called
# DESTROY() called
# eval-END

Zwischen dem Aufruf von bar() und eval-end mogelt sich das DESTROY frech dazwischen. Es gibt hier eine Nebenläufigkeit, da $foo am Ende des Eval-Blockes nicht mehr existiert und der GarbadgeCollector artig DESTROY() auf das Objekt aufruft. Da es aber nur eine Kopie von $@ gibt, wird der Inhalt durch das DESTROY-eval{} einfach überschrieben, denn das soll eval{} bei jedem Aufruf ja auch tun.

In der “Fehlerbeschreibung” ist als mögliche Lösung angegeben, man soll immer local verwenden, um z.B. $@ nicht zu überschreiben. Um auf “Hat Fehler” zu prüfen würde ich lieber ein
my $ok = eval {
#...some code
return 1;
};

verwenden, da man da erst gar nicht auf $@ angewiesen ist. Der Inhalt von $@ wird damit aber immer noch überschrieben werden.

Dieses Verhalten hat mir mal wieder gezeigt, dass mit allen Spezialmethoden von Perl, also import(), BEGIN, CHECK, END, INIT, DESTROY() und alle anderen, die zu bestimmten Ereignissen aufgerufen werden, immer sehr umsichtig umgegagnen werden muss. Am besten viele Diagnosemeldungen ausgeben lassen, damit man nicht den Überblick verliert.

Abgelegt unter Perl, Perl::Kinky | 1 Kommentar »

Merkwürdiges Verhalten bei lvalue

Erstellt von Tobi am 11. Juli 2008

Ich habe mich heute mal mit dem lvalue Attribute auseinander setzen müssen und einige Unstimmigkeiten festgestellt.

  1. Es macht einen Unterschied, ob man einen Rückgabewert implizit oder explizit liefert. Implizit, also einfach der letzte Wert der Methode, funktioniert für lvalue. Explizit, also mit return, funktioniert nicht. Return legt wohl generell eine Kopie der Rückgabevariablen an, denn bei dem unten stehenden Code wird mit return der Wert der Variablen nie gesetzt. Es wird aber auch kein Fehler geworfen. Dieser Unterschied erklärt auch, warum das implizite return bei Lasttests um ein vielfaches schneller ist. Für die Erbsenzähler :) das liegt im unteren µs Bereich, Für normale Programmierarbeiten also uninteressant.
  2. Beim Dekorieren einer lvalue-Methode kann kein goto SUBREF verwendet werden. Dort gibt es eine Fehlermeldung, dass eben dieses goto nicht umgebogen werden kann, wobei goto in der Form ja einfach den Kontext wechselt und an der anderen Stelle weiter macht. Was also passiert da im Hintergrund? Und: will ich das wirklich wissen oder kann ich danach nie mehr ruhig schlafen?
  3. Die Stelle, die die goto-Fehlermeldung wirft, ist entweder zu dumm oder zu schlau. Nach einem solchen goto wird ja in den Kontext der angegebenen Methode gewechselt und ALLE Codezeilen nach diesem goto werden nicht mehr ausgeführt. Wenn ich in die lvalue-Methode aber ein return nach dem goto setze oder irgend einen impliziten Rückgabewert, dann meckert er nicht und alles Funktioniert, wie es soll. Ich traue mich einfach nicht, in den C-Code zu sehen… *grusel*
  4. Die Art des Wertes, den die lvalue-Methode liefert spielt ja eine Rolle. Es muss zwingend eine änderbare Variable, also z.B. $text und NICHT ‘text’, sein. Das ist ja verständlich, da dieser Variablen ja ein Wert zugewiesen werden soll. Ein undef liefert auch eine Fehlermeldung. Ein return undef; oder return (); geht aber wieder. Was liegt da für ein Code dahinter, dass das so merkwürdig reagiert? Vielleicht wird ja bei explizitem return alles in eine temporäre Variable gesetzt, aber wozu? Fragen über Fragen…
  5. Noch so nebenbei, weil ich dadurch einige Zeit nach einem Fehler gesucht habe: in der MODIFY_CODE_ATTRIBUTES Methode, mit der Attribute selber definiert und behandelt werden können, kommen Perl-Interne Attribute, also z.B. lvalue, nie an.

Hier mal ein Code-Beispiel für die ersten zwei beschriebenen Verhaltensweisen:
use strict;
use warnings;
use Test::More 'no_plan';
our $bar;
sub foo : lvalue {
$bar;
#return $bar; # Uncomment this line will result in an error (1)
}
sub bar : lvalue {
goto &foo;
die 'ha!'; # never reached
return;# Remove this will raise an error (2 + 3)
}
is(bar() = 'baz', 'baz', 'set');
is(bar(), 'baz', 'get');

Und eine letzte Frage: gibt es irgendwo eine detaillierte Doku dazu oder ist es wie mit PerlXS ala “wurschtel dich halt durch”?

Abgelegt unter Perl::Kinky | 1 Kommentar »

Scalar::Util und die Hoffnung

Erstellt von Tobi am 30. Juni 2008

Über ein Modul im CPAN, wo sich über die direkte Benutzer von UNIVERSAL::isa als statische Methode beschwert wurde, wurde ich auf das Modul Scalar::Util aufmerksam. Das wurde nämlich als sichere Alternative genannt um heraus zu finden ob ein Scalar “geblessd” wurde und wenn ja, mit was.

Generell finde ich die direkte Benutzer von UNIVERSAL-Methoden auch unpraktisch, weil man z.B. Dekorierern so jede Chance nimmt, sich als fremde Klasse aus zu geben. Also habe ich mir die Methode mal angesehen, und fand folgenden Code vor:

[...]
eval < <'ESQ' unless defined &dualvar;
use vars qw(@EXPORT_FAIL);
push @EXPORT_FAIL, qw(weaken isweak dualvar isvstring set_prototype);
# The code beyond here is only used if the XS is not installed
# Hope nobody defines a sub by this name
sub UNIVERSAL::a_sub_not_likely_to_be_here { ref($_[0]) }
sub blessed ($) {
local($@, $SIG{__DIE__}, $SIG{__WARN__});
length(ref($_[0]))
? eval { $_[0]->a_sub_not_likely_to_be_here }
: undef
}
[...]

Quelle: http://search.cpan.org/~gbarr/Scalar-List-Utils-1.19/lib/Scalar/Util.pm

Ok, es wird nur verwendet, wenn der C-Code nicht ausgeführt werden kann, aber eine Methode a_sub_not_likely_to_be_here in Universal ungefragt zu hinterlegen um zu prüfen, ob es ein Objekt ist? Bäää! Von wegen Hope nobody defines a sub by this name. Wohl eher Hope nobody use this module, denn wenn der Perl-Code schon so aussieht… dann ist der C-Code wohl auch gaaanz toll. Hoffentlich?!?

Nur so nebenbei *klugscheiß*: Prüfungen auf “ist ein Objekt der Klasse” “ist ein Objekt” gehen ganz gut per Universal::isa(ref($obj), ‘UNIVERSAL’), aber das war in dem Fall wohl zu einfach (obwohl es auch passieren kann, dass es eine Klasse namens ‘HASH’ gibt, dann ist die Prüfung natürlich nicht korrekt). Aber man wollte wohl den statischen Aufruf von Universal::isa um jeden Preis umgehen.

Nachtrag: Man sollte den Code besser testen bevor man was schreibt. Auf ein Objekt kann sicher gestetet werden per die “obj ist ein Objekt” if(ref($obj) && UNIVERSAL::isa($obj, ‘UNIVERSAL’))

Noch mal zur Erinnerung, und für Micha: Perl rockt! Das CPAN aber nicht! Oder zumindest nur in sehr seltenen Fällen.

Abgelegt unter Perl::Kinky | 2 Kommentare »

Vorhandensein von Methoden in Klassen prüfen

Erstellt von Tobi am 17. April 2008

Wenn man wissen will, ob eine Methode in einer Klasse nur deklariert (sub method;) oder ob eben auch ein Methodenkörper existiert (sub method { die 1; }) sollte man sich nicht nur auf can() verlassen.

sub existing_sub;
sub defined sub { die 1; }
print __PACKAGE__->can('notexisting_sub'); #false
print exists(&notexisting_sub); # false
print defined(&notexisting_sub); # false
print __PACKAGE__->can('existing_sub'); #true
print exists(&existing_sub); # true
print defined(&existing_sub); # false
print __PACKAGE__->can('defined_sub'); #true
print exists(&defined_sub); # true
print defined(&defined_sub); # true

Der Aufruf einer Methode, die nur deklariert, führt zu einem “Undefined subroutine .. called” fehler, den man ja eigentlich mit dem can() verhindern wollte. Mir ist noch nie eine solche, blanke Deklaration über den Weg gelaufen, aber man weiß ja nie.

Abgelegt unter Perl, Perl::Kinky | 2 Kommentare »

Perl und Prototypen

Erstellt von Tobi am 14. April 2008

Nur vorab: ich bin nicht gegen Perl, sondern eigentlich dafür :)

Punkt eins: You see, “prototypes” were really a bug fix. (Quelle: http://library.n0i.net/programming/perl/articles/fm_prototypes: Far More Than Everything You’ve Ever Wanted to Know about Prototypes in Perl die Quelle wurde leider entfernt) Das ist schon mal irgendwie nicht so toll.

Punkt zwei: ich habe versucht die bless() Funktion zu dekorieren und musste mit erschrecken feststellen, dass deren Prototyp mal wieder voller (Schwarz-)Magie steckt. Definieren kann man folgendes in einem Prototyp:

@

Der “Rest” der Parameter. Schnappt sich ALLE Parameter und fügt sie ganz normal an @_ an, Macht nur am Ende eines Prototyps Sinn. Eine normale Funktion kann also ohne Prototyp oder mit sub test(@) {} geschrieben werden. Das macht keinen Unterschied.

%

Gleichbedeutend mit @

\@

Ein Array, und zwar wirklich eine Variable die mit @ beginnt, wie z.B. @test. Listen sind nicht erlaubt. (1,2,3) geht nicht! %test, was intern ja auch nur eine Liste ist, auch nicht. In @_ wird eine Referenz auf das Array als einzelner Parameter eingefügt.

\%

Ein Array, und zwar wirklich eine Variable die mit % beginnt, wie z.B. %test. Listen sind nicht erlaubt. (a => 1, b => 2, c => 3) geht nicht! In @_ wird eine Referenz auf den Hash als einzelner Parameter eingefügt.

$

Ein Scalar. Steht an der Stelle ein Array, ein Hash oder eine Liste, wird in @_ nur die Anzahl der Elemente eingefügt. Ansonsten der Wert des Parameters. Es ist im Grunde so, als würde man um seinen Parameter ein scalar() schreiben

\$

Eine Scalar, KEIN reiner Text! Es MUSS eine Variable sein die mit $ beginnt. In @_ wird eine Referenz auf den Scalar eingefügt

\*

Ein Glob, und NUR ein solcher. Nicht etwa “irgendeine Variable”. In @_ wird eine Referenz auf den Glob eingefügt

*

Ein Glob ODER alles andere. Bei einem Glob wird in @_ wird eine Referenz auf den Glob eingefügt. Bei einem Array oder einer Liste, die hier mal funktioniert, verhält es sich wie $. Bei einer Subroutine verhält es sich wie & (bzw auch $ macht da das gleiche).

&

Eine Funktionsreferenz oder ein Block. Wenn per sub {…} die Funktion als Referenz übergeben wird, wird die Referenz einfach in @_ gehängt. Wird das sub weggelassen, also nur ein Block übergeben, wirds ganz verrückt. Dann MUSS beim Funktionsaufruf die Klammer weggelassen werden, das Komma nach dem Block weggelassen werden und es MUSS der erste Parameter sein. grep und sort sind z.B. so Kandidaten, die das verwenden.

\&

Gleichbedeutend mit &

;

Trennt Pflich- von den optionalen Parametern

Bei bless() ist es nun so, dass der Prototyp eigentlich bless(\@;\@) ist, aber eben auch wieder nicht. Denn in dem speziellen Fall erlaubt das \@ auch Listen. Das Führt zwar am Ende zu einem Laufzeitfehler, funktioniert aber erst mal.

Und auch fraglich ist die Tatsache, dass der zweite Parameter von bless() ebenfalls eine Liste sein kann. Das letzte Element ist dann die “Klasse”, mit der geblesst wird. Aber was ist mit dem Rest? Ist das einfach ein Fehler oder passiert damit im Hintergrund irgendwas … ganz tolles?!?

Und das wichtigste: schlafe ich vieleicht einfach besser, wenn ich in dem Punkt unwissend bleibe?

Punkt 3, nur der vollständigkeit halber: Prototypen greifen nur, wenn man die Funktion direkt und ohne & benutzt. Indirekt, per OO also, oder per &$sub_ref() oder ähnliches geht nicht. Prototypen sind also NUR für global verfügbare Funktionen, oder Funktionen der eigenen Klasse sinnvoll (oder auch nicht).

PS: auch noch schon und auch aus obiger Quelle Since you gave Perl a ridiculous request, Perl dutifully provides you in return a ridiculous response–but not an error; oh no, not that!

Nachtrag: bless() hat den Prototyp (\@;$), nur verhält sich $ anders als gedacht. Steht dort eine Liste, wird das letzte Element verwendet. Ist es ein @Array, dann wird die länge ausgegeben. Ist das nicht toll?

Abgelegt unter Perl::Kinky | 4 Kommentare »