ubuntuusers.de

Perls Zuweisungsoperator inkonsequent?

Status: Ungelöst | Ubuntu-Version: Ubuntu
Antworten |

FLoH.tar

Anmeldungsdatum:
6. Januar 2006

Beiträge: 470

Hallo,

$ perl -e 'print +( *false = undef ) ? "Du wirst heute einen Alb träumen" : "Du wirst heute gut schlafen!", "\n";'
Du wirst heute einen Alb träumen!
$ perl -e 'print +( $false = undef ) ? "Du wirst heute einen Alb träumen" : "Du wirst heute gut schlafen!", "\n";'
Du wirst heute gut schlafen!

Offenbar gibt eine Zuweisung an einen Typeglob selbst bei einem falschen r-value wahr zurück. Eine Zuweisung von undef an eine skalare Variable dagegen ist falsch, während man doch in perlop, Kap. "Assignment Operators" liest:

Unlike in C, the scalar assignment operator produces a valid lvalue.

Bin ich hier auf einen Bug gestoßen oder wird das absichtlich unterschieden?

FLoH.

Sid_Burn

Anmeldungsdatum:
23. Oktober 2004

Beiträge: 2159

Zuerst mal hierzu:

$ perl -e 'print +( $false = undef ) ? "Du wirst heute einen Alb träumen" : "Du wirst heute gut schlafen!", "\n";'

Das erste was passiert ist das deinem Skalar $false den Wert undef bekommt. Es besitzt einen undefinierten Wert. Deine Kommentierte Ausgabe ist folgendermaßen zu verstehen, dass die Variable $false trotzdem erfolgreich angelegt wird. Aber darauf wo false Zeigt ist ein undefinierter "Falscher" Wert.

Was dort also letztendlich steht ist "$false ? ..." da du einen undefinierten Wert hast, ist das ganze false, und es wird, "Du wirst heute gut..." ausgegeben.

perl -e 'print +( *false = undef ) ? "Du wirst heute einen Alb träumen" : "Du wirst heute gut schlafen!", "\n";'

Hier fast das selbe, allerdings steht dann nachher soetwas da wie folgendes. "*false ? ..." Am besten ist du gibst den Wert einmal mit print zurück "print *false" du wirst dann folgendes sehen. "*main::false".

Die Funktionsweise vom Typeglob ist folgende das du sozusagen einen Alias auf etwas damit anlegen kannst, ähnlich wie Referenzen. Besser ist es du benutzt mal unterschiedliche namen.

*false = "Hallo";
print *false

Die ausgabe ist dann "*main::hallo". *false wurde also zu einem Alias auf "*main::hallo". Und dieses ist nunmal ein Wahrer Wert. Wenn du "undef" zuweist wird der eigene Name zurück gegeben. "*main::false". Also auch wieder ein wahrer Wert. Wenn du übrigens "use warnings" oder "-w" benutzt, bekommst du übrigens bei dem Code einen Hinweis. "Undefined value assigned to typeglob at ./typeglob.pl line 5."

Um das nochmal mit "$false = undef" zu erläutern. Die variable an sich wird hier nicht vernichtet. Die variable wird erfolgreich erstellt, und ist vorhanden, und das ist gemeint mit "valid lvalue". Wenn du allerdings auf den Inhalt von $false zugreifst dann bekommst du "undef", also einen falschen Wert.

Wofür brauchst du überhaupt Typeglobs?

FLoH.tar

(Themenstarter)

Anmeldungsdatum:
6. Januar 2006

Beiträge: 470

Danke, jetzt verstehe ich. ☺

Ich verwende Typeglobs, um in einer Extradatei vorliegenden Code für eine bestimmte Funktion zur Laufzeit quasi-inline einzuhängen:

{ open SLURP, $file or suicide; local $/; *rec_to_be_processed = eval "sub () {" . <SLURP> . "}" || warn $@; close SLURP } 


Ich weiß, jetzt kreischen die Verfechter der Perlehre auf, wegen Pakete/Module, export und so. Aber ich versuche, zweckorientiert anschaulich zu programmieren, und dazu gehört, dass das Hauptprogramm die Funktion namentlich definiert, während die Extradatei ausschließlich als Code-Lieferant dient und dabei wie ein ganz normales, schickes und einfaches selbstständiges Perlskript ausschaut.
Denn wegen des bisschen Codes, der durch SLURP flutschen wird, sollte ich die, für die ich das Ding schreibe und die kaum Ahnung von Perl und Programmierung haben, nicht noch mit der Pakettechnologie verwirren. Die Erklärung "Der Code der Filterfunktion rec_to_be_processed() wird aus der Datei Filtername.fil.pl gelesen, so auf der Kommandozeile zu perl procdmp.pl die Option -f Filtername angegeben wird (sonst bleibt sie inaktiv)." erachte ich für den Laien intuitiver nachvollziehbar, oder zumindest klingt es eleganter als "Die Funktion rec_to_be_processed() ist in der Datei Filter.pm definiert. Bei jedem Wechsel des Filters zu Testzwecken musste ich dazu diese Datei verändern oder im Hauptprogramm eine alternative Moduldatei angeben." Es ist ja nicht nur der Filter, der auf diese Weise definiert wird, sondern auch die Konvertierung und die Ausgabe. So kann ich alle drei Teile weitgehend voneinander getrennt programmieren und für jeden Durchlauf entscheiden welche konkreten Module zu verwenden sind. Ein extrem flexibles Programm also.

So habe ich mir das jedenfalls vorgestellt, und abgesehen davon, dass der einzuhängende Code natürlich funktionsfähig sein muss (irgendwas läuft da noch verkehrt), scheint es zu funktionieren. Seht ihr bei dieser Lösung etwaige Probleme auf mich zu kommen?

FLoH.

Sid_Burn

Anmeldungsdatum:
23. Oktober 2004

Beiträge: 2159

Wenn ich dich richtig verstanden habe möchtest du einfach nur Perl Code einbetten, und die Funktionen die dort definiert sind möchtest du in deinem Programm nutzen?

Du hast also unterschiedliche Dateien die dir jeweils z.B. 3 mal die selben Funktionen zur Verfügung stellen, und je nach aufruf soll die eine oder andere Datei geladen werden. Die Funktionsnamen sind identisch jedoch macht der Code etwas anderes?

Reicht da nicht ein normalen "require" ?

main.pl

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

if    ( $ARGV[0] == 1 )  { require "filter1.pl" }
elsif ( $ARGV[0] == 2 )  { require "filter2.pl" }

ausgabe();

filter1.pl

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

sub ausgabe {
    print "Hallo, Welt.\n";
}

1;

filter2.pl

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

sub ausgabe {
    print "Etwas anderes.\n";
}

1;

"./main 1" gibt dann "Hallo, Welt!" aus.
"./main 2" gibt dann "Etwas anderes." aus.

Da require erst zur Laufzeit ausgewertet wird könntest du auch sowas machen.
main.pl

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

require "$ARGV[0].pl";

ausgabe();

./main filter1
./main filter2

Oder habe ich dich jetzt Falsch verstanden?
Und ich denke ein Modul wäre sauberer. 😉

FLoH.tar

(Themenstarter)

Anmeldungsdatum:
6. Januar 2006

Beiträge: 470

Wenn ich dich richtig verstanden habe möchtest du einfach nur Perl Code einbetten, und die Funktionen die dort definiert sind möchtest du in deinem Programm nutzen?

Ja. Nein. Das Missverständnis liegt in "und die Funktionen die dort definiert sind". Sie sind eben nicht "dort" definiert, sondern im Hauptprogramm, wo die Definition der Subroutine genau genommen durch eval erledigt wird. "Dort" enthält bloß tumben, linearen perligen batch-code, nichts #!/was/auch/immer, nichts use dingsta, nichts sub sowieso { ... }, von der leidigen 1; ganz zu schweigen. "Dort" sind nur passive Daten, die zufällig perlverdaulich sind, ohne verwirrenden geekigen Overkill. Gerade diese Einfachheit in Hinsicht auf die Moduldateien ist mir hier wichtiger als Konventionalität.

Könnte dieser Ansatz nicht fallweise sogar dem modularen Paroli bieten?

sub dance :import $ARGV[0] ();        # Code von dance() wird nicht hart kodiert, sondern compile-time aus der Datei $ARGV[0] gelesen

FLoH.

Sid_Burn

Anmeldungsdatum:
23. Oktober 2004

Beiträge: 2159

Okay,
sofern ich dich jetzt dann richtig verstehe, brauchst du aber dann immer noch kein Typeglob dafür. Wenn du eval() ausführst, dann führst du den Code ja aus, und es gehört dann zu deinem Programm so als hättest du es direkt geschrieben.

main.pl

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

open  FILE, "<", "filter1.pl";
my @FILE = <FILE>;
close FILE;

eval "sub ausgabe { @FILE }";
die $@ if $@;

ausgabe();

filter1.pl

print "Foo.\n";
print "bar.\n";
print "Hallo, Welt!";

filter1.pl enthält jetzt nur noch einzelne Perl Befehle, und du sparst dir den Typeglob, und sieht auch übersichtlicher aus, meiner Meinung nach. Aber es gibt ja mehrere Wege, wenn dein Weg so klappt, ist da ja nichts dran verkehrt. 😉

EDIT:

sub dance :import $ARGV[0] ();

Solch eine Funktion kannst du dir aber auch eben selber schreiben:

main.pl

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

sub import {
    my $sub  = shift;
    my $file = shift;

    local $/ = undef;

    open my $fh, "<", $file;
    my $input = <$fh>;
    close $fh;

    eval "sub $sub { $input }";
    die $@ if $@;
}

import 'ausgabe', 'filter1.pl';
ausgabe();

filter1.pl

print "Foo.\n";
print "bar.\n";
print "Hallo, Welt!";

FLoH.tar

(Themenstarter)

Anmeldungsdatum:
6. Januar 2006

Beiträge: 470

Aha, stimmt, eigentlich brauche ich keinen Typeglob, wenn ich über eval keine Closure, sondern eine "echte" Funktion definiere. Aber nun klappt es auch so, trotzdem danke, nächstes Mal, wenn es nicht bei nur drei Funktionen bleibt, mache ich es so. ☺ FLoH.tar

Sid_Burn

Anmeldungsdatum:
23. Oktober 2004

Beiträge: 2159

Aha, stimmt, eigentlich brauche ich keinen Typeglob, wenn ich über eval keine Closure, sondern eine "echte" Funktion definiere.

Das ganze geht halt deswegen, weil Subroutinen immer für das ganze Package gelten, egal wo du sie definierst. Es gibt halt keine lexikalischen "benannten" Subroutinen.
Bei annonymen ist das ja wieder anders.

FLoH.tar

(Themenstarter)

Anmeldungsdatum:
6. Januar 2006

Beiträge: 470

Ich habe das ganze doch mal kurz mit require getestet und weiß jetzt, dass ich mir damit zwar die ganzen Zeilen für den Code-Import via eval() sparen würde, dafür jedoch ALLE Variablen des Hauptprogramms globalisieren müsste, um aus dem importierten Code darauf zugreifen zu können, denn require basiert ja auf do FILE. Da our() aber alle Namensräume infiltriert, und ich nicht ausschließen kann, dass ich vielleicht doch ein Modul aus der Perl-Bib verwende, ist mir dies ein Gräuel.

FLoH.tar

Sid_Burn

Anmeldungsdatum:
23. Oktober 2004

Beiträge: 2159

FLoH.tar hat geschrieben:

Ich habe das ganze doch mal kurz mit require getestet und weiß jetzt, dass ich mir damit zwar die ganzen Zeilen für den Code-Import via eval() sparen würde, dafür jedoch ALLE Variablen des Hauptprogramms globalisieren müsste, um aus dem importierten Code darauf zugreifen zu können, denn require basiert ja auf do FILE. Da our() aber alle Namensräume infiltriert, und ich nicht ausschließen kann, dass ich vielleicht doch ein Modul aus der Perl-Bib verwende, ist mir dies ein Gräuel.

FLoH.tar

Ne das stimmt so nicht ganz. Ein our() infilitriert nicht alle Namensräume und macht die Variable auch nicht überall Verfügbar. Eine globale Variable bei Perl bedeutet lediglich das diese variable egal wo du dich gerade befindest, immer von überall aufrufbar und veränderbar ist. Es bedeutet nicht das diese Variable in jeden namensraum existiert.

Denn Perl Header lasse ich mal weg, ich gehe davon aus das alle Skripte mit "use warnings" und "use strict" laufen.

our $string


Die variable $string an sich ist damit für die ganze Datei gültig. Das heißt wenn du in dieser Datei lediglich $string schreibst benutzt du diese "Globale" Variable. Es wird aber nur dann die Globale Variable genommen wenn es keine lexikalische Variable gibt, und auch nur dann wenn sich die our() Deklaration in der selben Datei befindet.

$string wird deswegen zu einer Globalen Variable, weil du mithilfe des package Namen also "$main::string" darauf zugriff hast. Es bedeutet nicht das $string jetzt innerhalb des package Net::FTP etc. existiert, dort ist die Variable völlig unbekannt. Du hättest aber aus dem Package Net::FTP über den Namen $main::string die Möglichkeit die Variable zu verändern. Das ist lediglich mit Global gemeint.

Ansonsten ist der Variablenname $string auch nur innerhlab der selben Datei gültig. Das ganze geht sogar soweit das es selbst für das selbe package gilt.

test.pl

our $string = "Hallo, Welt!\n";

main.plx

require "test.pl"
print $string;

Würde mit "use strict" in einem Kompilierfehler enden. Obwohl du sogar $string im selben package "main" angelegt hast, ist dieser Name in der Datei main.plx nicht verfügbar. Du kannst nur darauf zugreifen wenn du explizit den Globalen Namen benutzt. Sprich in main.plx dann "$main::string"

Global gültig bedeutet wie gesagt nur das es einen absoluten Namen gibt von dem du deine Variable immer verändern kannst. Das bedeutet aber nicht das die Variable deswegen sofort in jeder Datei vorhanden ist, weder noch im jeden package.

Wenn du es so siehst gibt es eigentlich gar keine richtigen Globalen Variablen in Perl.

Ansonsten solange du in main.plx auch keine neue our Deklaration machst, funktioniert dein Code auch normal weiter wie erwartet.

test.pl

our $string = "Hallo, Welt\n"

main.plx

require "test.pl"
my $string = "Leer\n";

print $string;
print $main::string;

Dieses würde dann folgendes ausgeben:

Leer
Hallo, Welt\n"

Weiterhin werden lixkalische Definitionen immer vor Gloabalen benutzt.

our $string = "Hallo, Welt!\n";
{
    my $string = "foo\n";
    print $string;
}
print $string;


Dies würde dann folgendes ausgeben:

foo
Hallo, Welt!


Möchtest du wie gesagt die globale und nicht die lexikalische Variable benutzen musst du den kompletten Namensraum angeben. In diesem fall also wieder "$main::string"

Ansonsten hat require aber noch eine eigenheit die sich von do() unterscheidet. Den es bindet jede Datei nur einmalig ein. Während do() die Datei immer wieder neu einbinden würde, was zu Problemen führen kann. Daher sollte man bei Bibliotheken schon require Benutzen. Oder bei richtigen OOP Bibliotheken natürlich "use" das dass require nochmals um paar Funktionen erweitert, und noch etwas mehr macht.

Ein weiteres Beispiel:

package foo;
our $string = "Hallo, Welt!\n";

package bar;
our $string = "Leer\n";

package main;
print $string;

print $foo::string;
print $bar::string;


Hier definierst du nicht eine Globale Variable, und diese Globale Variable wird auch nicht neu definiert. Du definierst zwei unterschiedliche Variablen.

1) $foo::string
2) $bar::string

Wenn du einfach nur "$string" schreibst, dann werden eigentlich erst lexikalische Variablen benutzt, und wenn es diese nicht gibt, die letzte Globale Definition in der aktuellen Datei, es würde als im ersten print "Leer" ausgegeben, wenn du das ganze wieder in Dateien aufteilst, geht das ganze wieder nicht. Von daher solltest du Globale Variablen auch immer mit dem kompletten Namen ansprechen, wenn du eine Globale Variable benutzen möchtest.

Ansonsten hat wie gesagt eine Globale Variable $foo::string keine Auswirkung auf dem Namesbereich Net::FTP etc. Von daher kannst du so viele Module benutzen wie du möchtest, ohne negative Auswirkungen. Es gibt halt an sich keine globalen Variablen. Global bedeutet bei Perl lediglich das du einen absoluten Namen besitzt von wo aus du immer direkt auf die Variable zugreifen kannst.

Und selbst wenn du hingehst und im Namensraum Net::FTP weitere globale Variablen definierst würde das keine Auswirkung auf die Methoden dort im package haben. Da lexikalische vor Globalen Variablen Namen genommen werden. Du würdest nur dann die Globale Variable nehmen wenn du sie, wie gesagt, explizit über den Kompletten Namen "$Net::FTP::variable" etc. anforderst.

Antworten |