Ruby 2.0 als Geschenk zum 20. Geburtstag

Seite 2: Enumerator#Lazy & keyword arguments

Inhaltsverzeichnis

Die "faule" Auswertung von Listen ist eine Erweiterung, die vor allem Programmierer mit Kenntnissen in funktionalen Programmiersprachen freuen wird. Sie ermöglicht eine äußerst kompakte Schreibweise für das Abarbeiten von Schleifen und (unendlichen) Collections, da temporäre Arrays nicht mehr nötig sind. Beispielsweise lässt sich:

require 'prime'
prime_array = []
Prime.each do |value|
next if value % 4 != 3
prime_array << value
break if prime_array.size == 5
end

durch

'Prime.lazy.select {|value| value % 4 == 3 }.take(5).to_a'

ersetzen. Das richtig Interessante neben der Schreibweise ist aber die Tatsache, dass Entwickler, wie im zweiten Fall, die Enumeratoren verketten können. Die Auswertung geschieht dann nicht mehr direkt, sondern ein "lazy" Enumerator liefert wiederum einen weiterverwendbaren Enumerator::Lazy zurück. Erst wenn man tatsächlich einen Wert zurückbekommt, wird der Ausdruck ausgewertet. Den Vorteil verdeutlicht die Betrachtung folgender Tests:

require 'test/unit'
include Test::Unit::Assertions
a = [1,2,3,4,5,6]
// Ohne lazy wird das Array komplett abgearbeitet.
assert_equal(4, a.select {|x| x > 3}.first)
// Mit lazy stoppt die Abarbeitung, sobald der erste Wert
// größer 3 gefunden wurde
assert_equal(4, a.lazy.select {|x| x > 3}.first)

Für die oft verwendeten Methoden map, flat_map, select, reject, grep, zip, take, take_while, drop, drop_while und cycle gibt es ebenfalls eine Enumerator::Lazy-Implementierung.

Trotz der Vorteile durch kompakte Schreibweise, Verkettung und Abarbeiten unendlicher Collections darf eines nicht unerwähnt bleiben: Die Abarbeitungsgeschwindigkeit kann sich ungefähr um den Faktor 4 verlangsamen. Der Grund ist, dass während der Verkettung von Enumeratoren die
Teilergebnisse im Hauptspeicher verbleiben müssen, da sie als Parameter in der weiteren Abarbeitung verwendet werden.

In Ruby 2.0 findet man ein neues, in einigen anderen Programmiersprachen ebenfalls bekanntes Feature: die auch "named parameters" genannten "keyword arguments". Bislang hat man in Ruby über eine Notlösung ein ähnliches Verhalten implementieren können, indem ein Hash beim Methoden-Aufruf übergeben wurde. Wollte der Entwickler Default-Werte verwenden, wurden diese meist über merge() mit den eigentlichen Argumenten zusammengeführt.

def config(opts={})
default_values = {ssl: true, timeout: 500}
opts = default_values.merge(opts)
end

config
#=> {:ssl=>true, :timeout=>500}

config :ssl => false, :timeout => 200
#=> {:ssl=>false, :timeout=>200}

Auf diesen "Hack" lässt sich dank des neuen Features nun verzichten. In einem Methodenaufruf kann der Entwickler für die Parameter direkt Namen und in der Methoden-Deklaration für diese Argumente dementsprechend auch Default-Werte angeben. Die nachfolgenden Beispiele verdeutlichen die Möglichkeiten.

def phrase(question: 'The answer to life the universe and everything',
answer: '42')
[question, answer]
end

phrase
#=> ["The answer to life the universe and everything", "42"]

phrase question: 'The answer'
#=> ["The answer", "42"]

phrase question: 'The answer', wrong_answer: '43'
#=> ArgumentError: unknown keyword: wrong_answer

Der Entwickler kann auch optionale Parameter angeben, die direkt als Hash-Wert interpretiert werden. Dafür schreibt er vor den eigentlichen Parameter ein **. Das wandelt die optionalen Parameter direkt in einen Hash-Wert um, was praktisch sein kann.

def config(ssl: true, timeout: 500, **options)
[ssl, timeout, options]
end

config
#=> [true, 500, {}]

config ssl: false, timeout: 100, test_mode: true
#=> [false, 100, {:test_mode=>true}]

Zum Schluss noch ein etwas kryptischeres Beispiel in Verbindung mit dem Splat-Operator.

def config(*clients, ssl: true, timeout: 500, **options)
[clients, ssl, timeout, options]
end

config
#=> [[], true, 500, {}]

config 'foo', 'bar', ssl: false, timeout: 100, test_mode: true,
max_response: 50
#=> [["foo", "bar"], false, 100, {:test_mode=>true, :max_response=>50}]

Weitere Möglichkeiten findet man in den Testfällen für die "keyword arguments".