Tipps und Tricks zu Perl und CPAN-Modulen

Skalar oder Array-Referenz gleich behandeln

Beitrag von Uwe am 22.03.2007 um 21:50 Uhr | 5 Kommentare

Folgende Situation: Bei der Parameterübergabe erlaubt man sowohl einen Wert (Skalar) als auch mehrere Werte (Array-Referenz). Nun möchte man bei der Auswertung nicht immer zwischen Skalar und Array-Referenz unterscheiden, zumal ja sicherlich der einzelne Wert nur die Sonderform einer einelementigen Liste ist.

Da ich dies heute gleich mehrfach hatte, habe ich mal in Scalar::Util und List::Util bzw. List::MoreUtils nachgeschaut. Dort gibt es viele nützliche Funktionen für Listen (z. B. min, max und shuffle) und Skalare (z. B. blessed und weaken), für mein Problem war aber nichts dabei.

Dabei ist es eigentlich ganz einfach (auch wenn es sich für einen Perl-Anfänger vielleicht nicht so einfach liest): Handelt es sich um einen Skalar, wird eine einelementige Liste erstellt, ansonsten wird die Array-Referenz dereferenziert und der Liste zugewiesen. Das Ergebnis ist also in jedem Fall eine Liste.

Als kleines Modul verpackt sieht das ganze zum Beispiel so aus:

 1  package MyUtil;
 2  $VERSION = 0.001;
 3  
 4  use strict;
 5  use warnings;
 6  
 7  use base 'Exporter';
 8  our @EXPORT_OK = qw(_list);
 9  
10  
11  sub _list {
12      my ($data) = @_;
13      return unless $data;
14  
15      my @data = ($data);
16      @data = @$data if ref $data eq 'ARRAY';
17  
18      return @data if wantarray;
19      return \@data;
20  }
21  
22  
23  1;

Download: MyUtil.pm

In den Zeilen 7 - 8 wird die Subroutine _list als exportierbar gekennzeichnet.

Wird _list nichts oder ein falscher Wert (z. B. leerer String) übergeben, dann wird in Zeile 13 gleich zurückgesprungen. Das Ergebnis ist dann eine leere Liste (Listen-Kontext) oder undef (skalarer Kontext). Wer ein einzelnes leeres Element zulassen möchte, der muß stattdessen "return unless defined $data;" verwenden.

In Zeile 15 erfolgt die Erzeugung einer einelementigen Liste. Ob es sich um eine Array-Referenz handelt, wird in Zeile 16 geprüft. In diesem Fall wird diese dann dereferenziert.

In Zeile 18 wird mit wantarray geprüft, in welchem Kontext (skalar oder Liste) die Funktion _list aufgerufen wurde. Im Listen-Kontext ist wantarray wahr und es wird das Array @data zurückgegeben. Anderenfalls wird eine Array-Referenz zurückgegeben. Wem das zu kompliziert ist, der verwendet einfach "return @data;" - dann wird eben immer eine Liste zurückgegeben.

In Aktion sieht das Ganze dann so aus:

#!/usr/bin/perl

use warnings;
use strict;

use MyUtil qw(_list);


my @list = _list(1);

@list = _list([1, 2, 3]);

Download: use-list.pl


Kommentar abgeben


hossa :)

Mir scheint es gerade so, dass du nen bissel groß auf das Problem schießt :)

sub irgendwas_tolles {
  my $array_ref = ref($_[0]) eq 'ARRAY' ? $_[0] : [$_[0]];
  # und schon hat man immer ne arrayref
  # mit mindestens einem Element

  return @$array_ref;
}

So ein Konstrukt habe ich in sehr vielen meiner Module, auf die Idee das nochmal durch eine Funktion zu schicken erscheint mir irgendwie zu umständlich :)

Oder hab ich was wichtiges übersehen? *grübel*

Kommentar von Sadrak am 23.03.2007 um 14:07 Uhr


Da war ich wohl selbst zu schnell gewesen ... das ganze hat natürlich noch ne Schwachstelle ...

was ist wenn die Funktion ohne alles aufgerufen wird? Richtig, man bekommt eine array_ref mit einem Element ... undef *g*

Und der Kommentar mit man hat immer ein Element stimmt ja auch nicht ... wenn man eine leere array_ref übergeben hat :)

ALSO:

sub noch_teile_viel_tollere_funktion {
  my $array_ref = ref($_[0]) eq 'ARRAY' ? $_[0] : [@_];

  return @$array_ref;
}

Kommentar von Sadrak am 23.03.2007 um 15:17 Uhr


Nein, Du hast nichts wichtiges übersehen :-)

Aber wenn Du es in vielen Deiner Module hast, dann kann man es ja auch auslagern (DRY - Don't repeat yourself). Und so kann ich gleich noch die Verwendung von Exporter zeigen.

Einen kleinen Vorteil hat _list vielleicht noch - man kann es in einer foreach-Schleife schachteln:

  foreach my $foo (ref($bar) eq 'ARRAY' ? @$bar : ($bar)) { ... }

Das gefällt mir nicht so gut ;-)

Kommentar von Uwe am 23.03.2007 um 16:15 Uhr


Nagut, einige Leute können das sicherlich gebrauchen und wenn man eh schon ein Helper-Modul benutzt, kann man da ja problemlos noch eine Funktion mehr reinnehmen ...

Aber was du an dem foreach-Teil nicht magst verstehe ich nicht, ich find das übelst sexy :-)

Kommentar von Sadrak am 23.03.2007 um 16:25 Uhr


Ich glaube, ich mag den ternären Operator " ? : " nicht so sehr :-)

Kommentar von Uwe am 23.03.2007 um 16:31 Uhr


Kommentar abgeben