Ruby 2.0 als Geschenk zum 20. Geburtstag

Am 24. Februar 2013 feierte die Ruby-Community den 20. Geburtstag der Programmiersprache. Zugleich erschien mit Ruby 2.0 ein neues größeres Release, das etliche spannende Neuerungen enthält.

In Pocket speichern vorlesen Druckansicht
Lesezeit: 12 Min.
Von
  • Tim Keller
  • Christian Albrecht
Inhaltsverzeichnis

Der 24. Februar 2013 markierte nicht nur die Freigabe der Version 2.0 der Programmiersprache Ruby, sondern zugleich auch ihr 20-jähriges Bestehen. Am 24. Februar 1993 gab es den ersten Check-in, und damit läutete Ruby-Erfinder Yukihiro "Matz" Matsumoto die Erfolgsgeschichte einer dynamisch typisierten Programmiersprache ein, die vor allem mit dem Webframework Ruby on Rails weltweit populär werden sollte.

Die Entwicklung von Ruby 2.0 ist die konsequente Weiterentwicklung der eher experimentellen 1.9er Versionen. Auf dem Weg von Ruby 1.9 zum neuen Ruby wurden knapp 800 Bugs behoben und über 320 kleine und große Features hinzugefügt. Zu den Neuerungen, die die Ruby-Programmierer am meisten beeinflussen werden, gehören sicherlich:

  • Refinements
  • Enumerator#Lazy
  • keyword arguments

Die folgenden Beispiele wurden mit der Ruby-Version 2.0.0dev (2013-02-08 trunk 39161) getestet.

Bei der RubyConf 2010 hatte Shugo Maeda zum ersten Mal das Konzept sogenannter Refinements in Ruby vorgestellt. Ähnliche Konzepte sind in anderen Programmiersprachen unter den Namen "Selector Namespaces" oder "Classboxes" bekannt. Die Grundidee hinter den Refinements ist es, das sogenannte Monkey-Patching sicherer zu machen, und zwar in dem Sinn, dass Klassenveränderungen zum Beispiel von Ruby-Core-Klassen durch Refinements nur in einem bestimmten Kontext Auswirkungen haben und nicht "global". Der Entwickler kann weiterhin Klassen zur Laufzeit verändern, verringert aber durch die Refinements das Risiko, dass sich Klassenerweiterungen verschiedener Bibliotheken negativ beeinflussen. Das ist sicherlich ein Problem des klassischen Monkey-Patchings und vor allem im Rails-Kontext anzutreffen, wo viele Konstrukte über Monkey-Patching realisiert werden.

Um ein Refinement zu erstellen, ist die Methode refine innerhalb eines Moduls zu verwenden. In diesem Fall ist refine kein Ruby-Schlüsselwort. Die Methoden-Signatur sieht folgendermaßen aus:

Module#refine(klass, &block)

Die Methode erwartet die zu erweiternde Klasse und einen Block. Module lassen sich nach derzeitigem Stand nicht übergeben. Im Block werden dann die eigentlichen Erweiterungen definiert. Laut Spezifikation kann der Entwickler mehrere refine-Statements in einem Modul verwenden.

#my_active_support.rb
module MyActiveSupport
refine String do
def blank?
self !~ /[^[:space:]]/
end
end
refine NilClass do
def blank?
true
end
end
refine FalseClass do
def blank?
true
end
end
refine TrueClass do
def blank?
false
end
end
end

Um die erstellten Refinements nutzen zu können, sind sie jetzt mit dem Aufruf using zu aktivieren, das sich als Soft-Keyword ansehen lässt.

#main.rb
require 'my_active_support.rb'

using MyActiveSupport

' '.blank?
#=> true

'Hello'.blank?

#=> false

nil.blank?
#=> true

Im obigen Beispiel sind die Refinements von MyActiveSupport ab dem Aufruf von using bis zum Ende der main.rb-Datei gültig. Es lässt sich daher sagen, dass Refinements eine Art lexikalischen Anwendungsbereich (Scope) besitzen. Das nachfolgende Beispiel soll das Ganze noch etwas verdeutlichen.

#main.rb
module MyActiveSupport
refine Array do
def sum
inject(:+)
end
end
end

def call_sum(ary)
ary.sum
end

using MyActiveSupport

ary = [5,5]
p ary.sum
#=> 10

p call_sum(ary)
#=> undefined method 'sum'

Am Beispiel erkennt der Leser gut die Auswirkungen eines Methodenaufrufs außerhalb des Refinement-Anwendungsbereichs. Da die Methode call_sum vor using definiert wird, steht die erweiterte Array Methode sum zum Zeitpunkt des Aufrufs nicht zur Verfügung. Das löst anschließend einen Laufzeitfehler aus.

Dass dieses Feature als experimentell gekennzeichnet wurde, obgleich es auf den ersten Blick ein konsistentes Verhalten zu haben scheint, hat "Matz" mehr oder weniger selbst beantwortet: "Since there still remains undefined corner case behavior in refinements, and the time is running out, I decided not to introduce full refinement for Ruby 2.0". Er bezieht sich dabei auf Punkte wie Performance oder auch die bislang unklare Vererbung von Refinements. Der JRuby-Entwickler Charles Nutter hat in seinem Blog eine etwas ältere Spezifikation der Refinements ebenfalls kritisch beäugt. Es bleibt also abzuwarten, in welcher Form die Refinements in späteren Ruby-Versionen vorliegen. Jeder sollte frühzeitig überlegen, ob er das Feature jetzt schon einsetzen möchte.