Die Programmiersprache Ruby

Blog| Forum| Wiki  

Inhaltsverzeichnis

Vorbemerkung

Dieser Artikel ist _in Arbeit_ und daher bei weitem nicht vollständig.

Enumerables in Ruby

Was in Java oder anderen Sprachen das Collection-API ist, sind in Ruby Enumerables. Der Name leitet sich aus der theoretischen Idee der rekursiv aufzählbaren Menge ("Recursively enumerable set") her. Hier zeigt sich, dass der Ruby-Kern hin und wieder mal Begrifflichkeiten der theoretischen Informatik orientiert, was manchmal etwas ungewohnt sein kann.

Dieser Artikel soll eine praktische Einführung in die Ideen hinter der Verarbeitung von Listen, Dictionaries (Hashes), unsortierten Mengen (Set) und selbst implementierte Enumerables geben. Ruby geht hier einen sehr eigenen Weg, denn das Enumerable-API basiert stark auf Konzepten, die funktionalen Programmiersprachen entlehnt sind. Daher kann die Arbeit für Nutzer anderer imperativer Programmiersprachen etwas ungewohnt sein, vor allem durch die Abwesenheit expliziter Schleifen.

Ein gutes Verständnis des Enumerable-API ist voll allergrößter Wichtigkeit für jeden Ruby-Programmierer!

Enumerables im Core-API

Enumerables spielen in Ruby eine zentrale Rolle. Das Core-API enthält folgende Enumerable-Klassen: Array, Hash, Range, File und Dir. In Ruby 1.8 kommt noch String dazu, der in 1.9 diesen Status allerdings verloren hat. Dafür kommen in Ruby 1.9 Enumerator, Enumerator::Generator und Enumerator::Yielder hinzu. Zu beachten ist, dass die Ruby-Implementierung von Set Teil der Standard-API ist, deren große Menge an Enumerable-Klassen hier nicht aufgelistet werden soll.

Alle Enumerable-Klassen teilen sich eine Eigenschaft: sie enthalten das Modul Enumerable. Dieses implementiert die Grundoperationen auf Enumerables, jedoch steht es jeder Klasse frei, diese selbst zu implementieren. Grundsätzlich sollten sie sich dabei aber an das von Enumerable vorgegebene Verhalten halten.

Für alle Core-Klassen gilt dass die meisten Enumerable-Methoden in C implementiert sind und damit recht effizient arbeiten - sie sind selbst geschriebenen also vorzuziehen.

Anwendungsbeispiele

Vorweg einige Anwendungsbeispiele, damit wir nicht ganz im Freien schweben. Wir werden uns im folgenden mit Problemen beschäftigen wie:

  • Auf alle Elemente eines Arrays eine Methode aufrufen:
1
2
3
ary.each do |obj|
  obj.activate!
end


  • Auf alle Elemente eines Arrays eine Operation anwenden und die Rückgabewerte sammeln:
1
2
3
[1,2,3].map do |number|
  number ** 2
end #=> [1,4,9]


  • Elemente ausfiltern:
1
2
3
[1,2,3].reject do |number|
  number > 2
end #=> [1,2]


  • Einen Array zu einem Wert reduzieren:
1
2
3
[1,2,3].inject(0) do |val, number|
  val + number
end #=> 6


Sowie beliebige Kombinationen, zum Beispiel die Summe aller Quadratzahlen eines Arrays:


[1,2,3].map{ |n| n**2 }.inject(0){ |v,n| v+n } #=> 14


Die Idee: map/filter/reduce

Die grundsätzliche Idee hinter dem Enumerable-API stammt auch aus der funktionalen Welt und ist daher nicht jedem geläufig: map/filter/reduce. Grundsätzlich ist die Idee folgende: So gut wie alle Operationen auf Enumerables sind durch 3 Vorgehensweisen beschreibbar. Diese drei sind:

  • map: Wende eine beliebige Funktion auf jedes Element des Containers an
  • filter: Wende eine Entscheidungsfunktion auf jedes beliebige Element eines Containers an. Gibt diese Funktion "true" zurück, entferne das Element. Behalte es andernfalls.
  • reduce: Wende eine Funktion auf den Container an, der ihn Schritt für Schritt auf einen Wert reduziert. Das Summenbeispiel zeigt solch eine Funktion.

Enumeratoren

Eigene Enumerables implementieren