Bereichsabfragen mit case-when
von Matthias Reitinger
am 07.04.08 um 07:05 (2023 Hits)
Voher
Im Thema „Text nach Uhrzeit, aber.. ?“ aus dem PHP-Forum ging es darum, Texte in Abhängigkeit von der Uhrzeit auszugeben. Eine von mir etwas abgewandelte und vereinfachte Variante des zur Diskussion gestellten Quellcodes sieht folgendermaßen aus:
Ist es zwischen 14:00 und 19:59 Uhr, soll also „Strawberry fields“ ausgegeben werden, zwischen 20:00 und 22:59 Uhr soll „Night on Earth“ erscheinen etc.Code PHP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php $datum = getdate(); $stunde = $datum['hours']; if ($stunde >= 14 && $stunde < 20) { echo "Strawberry fields"; } elseif ($stunde >= 20 && $stunde < 23) { echo "Pipe Dreams"; } elseif ($stunde == 23 || $stunde < 4) { echo "Night on Earth"; } elseif ($stunde == 4) { echo "This is hardcore"; } elseif ($stunde >= 5 && $stunde < 14) { echo "a dry cool place"; } ?>
Nachher
Code Ruby:
1 2 3 4 5 6 7 8 9 10 11 12 case Time.now.hour when 14...20 puts "Strawberry fields" when 20...23 puts "Pipe Dreams" when 23, 0...4 puts "Night on Earth" when 4...5 puts "This is hardcore" when 5...14 puts "a dry cool place" end
Was passiert?
case-when
Die Ruby-Lösung bedient sich eines case-when-Konstrukts. Es erinnert stark an die switch-case-Anweisung aus C-ähnlichen Programmiersprachen – und ist doch so viel mächtiger und eleganter:
Kein break und kein „Durchfallen“
In Sprachen wie PHP muss man nach jedem case-Block ein break setzen, damit zum Ende der switch-Anweisung gesprungen wird. Vergisst man das break, werden die Anweisungen der nachfolgenden case-Blöcke bis zum nächsten break durchlaufen (man spricht von einem „fall through“ – „Durchfallen“).
Vor allem für Anfänger kann dieses Verhalten zur allgemeinen Verwirrung beitragen. Das Setzen der breaks wird damit zu einer lästigen Notwendigkeit. Und für den seltenen Fall, dass der „fall through“ tatsächlich erwünscht ist, sollte man nicht vergessen, eine entsprechende Warnung als Kommentar zu hinterlassen.
Ruby geht hier einen anderen Weg und springt grundsätzlich zum Ende der case-Anweisung, sobald ein when abgearbeitet ist. Ein break wird damit größtenteils überflüssig und gibt es (in diesem Kontext) entsprechend in Ruby auch nicht.
Mehrere Alternativen pro when-Block
Wie in Zeile 6 zu sehen ist, kann ein when-Block mit mehreren „Mustern“ (mehr dazu im nächsten Abschnitt) versehen werden, die durch Kommata getrennt notiert werden. Von diesen muss mindestens eines passen, damit zu diesem Block gesprungen wird.
Test auf „Case Equality“ statt auf einfache Gleichheit
Dieser Punkt stellt die wohl entscheidendste Besonderheit dar und ist auch der Grund, warum Ruby es uns erlaubt, die Bereichsabfrage so elegant zu formulieren. Anstatt für jedes Muster den Vergleich muster == wert durchzuführen, ruft Ruby jeweils die „Case Equality“-Methode === des Muster-Objektes mit dem Wert als Parameter auf. Gibt diese Methode true zurück, passt das Muster und der entsprechende when-Block wird ausgeführt.
Was in der ===-Methode passiert, hängt dabei ganz vom Muster-Objekt bzw. dessen Klasse ab. Bei primitiven Datentypen wie Ganzzahlen oder Zeichenketten wird ganz konventionell auf Inhaltsgleichheit getestet. Komplexere Objekte können aber auch eine eigene Semantik definieren:Dadurch erweitern sich Ausdruckskraft und Einsatzmöglichkeiten von case-when-Konstrukten enorm.
- Reguläre Ausdrücke prüfen, ob sie auf eine Zeichenkette matchen
- Ranges (siehe nächster Abschnitt) prüfen, ob sie eine Zahl enthalten
- Klassen prüfen, ob ein Objekt eine Instanz ihrer selbst (oder einer Subklasse) ist
Ranges
Der zweite Baustein für unseren Code ist Rubys spezielle Syntax zur Notation von Ranges (Bereichen, Intervallen):Der Unterschied zwischen 1..10 und 1...10 ist also, dass 10 im ersten Bereich noch enthalten ist, in letzterem dagegen nicht.
- start..ende: Bereich von start (inklusive) bis ende (inklusive)
- start...ende: Bereich von start (inklusive) bis ende (exklusive)
Was haben wir gewonnen?
Ruby erlaubt es uns, bei Bereichsabfragen auf unübersichtliche if-elseif-Kaskaden mit uneinheitlichen Abfragen zu verzichten. Auch müssen wir uns dank der in Ruby integrierten Klasse Range keine großen Gedanken darüber machen, wie die Abfragen genau auszusehen haben (das war auch die Fehlerquelle im anfangs angesprochenen Thema). Anstatt fünf verschiedener Operatoren (>=, <, ==, &&, ||) benötigen wir im Ruby-Code keinen einzigen. Auffällig ist auch, dass wir mit Ruby keine lokale Variable anlegen mussten.
Insgesamt ergibt sich also ein Quellcode, der schlanker, einfacher zu lesen und beim Schreiben weniger fehleranfällig ist.
Ran an die Tasten!
So viel zu meiner Sicht der Dinge. Jetzt ist Deine Meinung gefragt! Ist der Code in der ursprünglichen Sprache vielleicht eleganter formulierbar? Gibt es noch Fragen zum Ruby-Code? Hast Du einen Fehler gefunden? Was es auch ist: scheue Dich nicht, einen Kommentar zu verfassen! Ich freue mich schon auf euere Reaktionen.
- Matthias






