Eine Bestandsaufnahme mit Ausblick auf die Zukunft

Was ist neu in Ruby?
Keine Kommentare

Am 25. Dezember 2017 wurde planmäßig die neue Ruby-Version 2.5.0 freigegeben. Wie jede Minor-Version bringt auch Ruby 2.5 einige Neuerungen mit sich, die dieser Artikel vorstellen möchte. Darüber hinaus werden die im Rahmen von 2.6 für dieses Jahr anstehenden Veränderungen nebst einem vorsichtigen Ausblick auf die langfristigen Ziele von Ruby im Hinblick auf Ruby 3 vorgestellt.

Seitdem im Februar 2013, am 20. Geburtstag von Ruby, die Version 2.0 der Programmiersprache veröffentlicht wurde, wird jedes Jahr zu Weihnachten eine neue Minor-Version fällig (Tabelle 1). Da Ruby nur einem modifizierten semantischen Versionsschema folgt, können auch in Minor-Versionen neben neuen Features kleinere Inkompatibilitäten auftreten (Furore machte etwa mit 2.4 die Streichung von Fixnum und Bignum). In Ruby 2.5 überwiegen jedoch klar die neuen Features. Lediglich eine nennenswerte Inkompatibilität ist zu verzeichnen, nämlich die Entfernung der Auflösung von Konstanten der obersten Ebene über Klassen. So war bis 2.5 Folgendes möglich:

class Foo
end

puts Foo::RUBY_VERSION
# => warning: toplevel constant RUBY_VERSION referenced by Foo::RUBY_VERSION
# => 2.2.9

Das war zwar schon immer schlechter Stil, jetzt ist damit aber Schluss, denn Ruby generiert in diesen Fällen eine ordentliche Exception der Klasse NameError.

Version Veröffentlicht Fehlerkorrekturen bis Sicherheitsfixes bis
2.0.0 24.02.2013 24.02.2015 24.02.2016
2.1.0 25.12.2013 30.03.2016 31.03.2017
2.2.0 25.12.2014 28.03.2017 31.03.2018
2.3.0 25.12.2015 voraussichtlich Ende März 2018 voraussichtlich Ende März 2019
2.4.0 25.12.2016 voraussichtlich Ende März 2019 voraussichtlich Ende März 2020
2.5.0 25.12.2017 voraussichtlich Ende März 2020 voraussichtlich Ende März 2021
2.6.0 voraussichtlich 25.12.2018 voraussichtlich Ende März 2021 voraussichtlich Ende März 2022

Tabelle 1: Derzeit unterstützte Ruby-Versionen (Quelle)

Interessanter als diese marginale Inkompatibilität sind die neu in Ruby 2.5 eingeführten Features. Eine wichtige, aber nicht auf Anhieb verständliche Änderung betrifft das Modul Enumerable. Zunächst muss man sich dafür kurz vor Augen führen, dass es in Ruby neben der gewöhnlichen, mit dem Operator „==“ durchgeführten Gleichheitsprüfung noch die sogenannte case-Gleichheit mit dem Operator „===“ gibt. Die case-Gleichheit wird – daher der Name – insbesondere vom case-Konstrukt geprüft und stellt eher einen Musterabgleich als eine Inhaltsprüfung dar. Die Änderung in Enumerable generalisiert den zuvor schon in der Methode Enumerable#grep vorhandenen Ansatz, die case-Gleichheit als Abkürzung anstelle eines Blocks zu benutzen, indem ein optionales Argument ausgewertet wird. Dies ermöglicht Code wie den folgenden, in dem anstatt eines aufwendigen Blocks einfach nur ein case-Gleicheit unterstützendes Objekt als Argument übergeben wird:

# statt: room_temperatures.all? { |t| (19..24).include?(t) }
room_temperatures.all?(19..24)

# statt: lines.none? { |l| l =~ /-- Mark – / }
lines.none?(/-- Mark --/)

Bereits seit Ruby 2.1 optimiert die Ruby-VM Strings, die mithilfe von #freeze eingefroren wurden, indem es sie intern nur ein einziges Mal speichert. Weil das ständige Tippen von .freeze aber beschwerlich ist, kommt kaum jemand in den Genuss dieser Performanzverbesserung. Daher wird mit Ruby 2.5 erstmals ein unärer Minus-Operator auf Strings definiert, der sich genauso verhält wie ein Aufruf von #freeze, aber angenehmer zu schreiben ist:

str1 = "foo".freeze
str2 = -"foo"
str1.equal?(str2) #=> true

Was hat sich sonst noch getan? Geht man nach der Temperatur der über eine Änderung geführten Diskussionen, so müsste als wichtigste Neuerung in Ruby 2.5 der Austausch der Quelle für Zufallszahlen der SecureRandom-Klasse gelten. Seit fast vier Jahren wurde darum gestritten, ob die Linux Man Page random(4) fehlerhaft sei und wenn ja, ob man deshalb zu Unrecht auf den qualitativ schlechteren Zufallszahlengenerator von OpenSSL ausgewichen sei. Die Diskussion war derart festgefahren, dass erst die Änderung der Man Page durch die Kernelhacker Ende 2016 die verhärteten Fronten aufweichen konnte: SecureRandom.gen_random präferiert jetzt den Zufallszahlengenerator des Betriebssystems vor demjenigen von OpenSSL. Die praktischen Konsequenzen, die sich daraus ergeben, stehen jedoch in einem auffälligen Missverhältnis zum Umfang der geführten Diskussion. Dennoch: Kryptografie mit Ruby ist wieder ein Stück sicherer geworden, was begrüßenswert ist.

Auch ein anderes, schon öfters vorgeschlagenes Feature hat es nunmehr endlich in die Sprache geschafft: die abgekürzte Benutzung von rescue und ensure in Blöcken. Es kommt häufig vor, dass man einen Block vollständig in ein begin/rescue/end-Konstrukt einpflegen muss. Die unnötige Schachtelung von Schlüsselwörtern kann nunmehr entfallen. Mit Ruby 2.5 ist beispielsweise Folgendes möglich:

my_method do
may_raise
rescue => e
puts "Exception: #{e.message}"
end

Verwandt mit der Behandlung von Exceptions ist das folgende, noch als experimentell bezeichnete Feature. In Anlehnung an Python wird die seit Anbeginn der Zeit genutzte Reihenfolge von Backtraces umgekehrt (Listing 1).

# Bis Ruby 2.5:
x.rb:2:in `a': Problem (RuntimeError)
  from x.rb:6:in `b'
  from x.rb:9:in `<main>'

# Ab Ruby 2.5:
Traceback (most recent call last):
  2: from x.rb:9:in `<main>'
  1: from x.rb:6:in `b'
x.rb:2:in `a': Problem (RuntimeError)

Neben diesen größeren Änderungen gibt es noch einige im Detail. So wird die Bibliothek PrettyPrint aus der stdlib nun automatisch geladen, und Arrays bekommen ein neues Methodenpärchen #prepend und #append als Aliasse für #unshift und #push.

Die nahe Zukunft: Ruby 2.6

Gemäß dem üblichen Turnus soll Ruby 2.6 am 25. Dezember veröffentlicht werden. Die wohl wichtigste Änderung stellt der erst kürzlich in den Trunk aufgenommene Just-in-Time-Compiler (JIT) von Takashi Kokubun und Vladimir Makarow dar, der schon seit Langem versprochen, aber bis dato nie umgesetzt worden war. Glaubt man den im Tracker veröffentlichten Benchmarks, so führt allein diese Änderung zu einer immensen Performanzverbesserung gegenüber den Vorversionen.

Eine andere Neuerung zeichnet sich bei einem typischen Anwendungsfall ab. Schon vor einiger Zeit wurde Symbol#to_proc eingeführt, um simple Blöcke, die einfach nur dieselbe Methode auf jedem Objekt einer Liste aufrufen, als map(&:name) abzukürzen. Dies funktioniert aber nur mit Methoden, die keine Parameter besitzen. Voraussichtlich beginnend mit Ruby 2.6 wird dieser Technik eine weitere Methode Array#to_proc an die Seite gestellt werden, die dann folgende sehr prägnante Konstruktion erlauben wird:

strings.map(&[:delete, "@"])
# Äquivalent zu:
strings.map { |x| x.delete("@") }

Auch in Sachen Metaprogrammierung tut sich etwas. Ruby 2.6 wird eine Methode Symbol.find erhalten, die zu einem String das dazugehörige Symbol heraussucht, falls es existiert. Neu aufgenommen wird ferner die Methode Module.used_modules, mit der die aktiven Refinements ausgelesen werden können. Eine weitere Methode Module.used_refinements, die die betroffenen Klassen ausliest, ist ebenfalls vorgeschlagen.

In eine ähnliche Richtung gehen die Pläne, dass die althergebrachten attr-Metamethoden sowie die Sichtbarkeitsschlüsselwörter private, public und protected die betroffenen Methodensymbole als Array zurückgeben sollen, was eine gewisse Symmetrie zum Schlüsselwort def herstellen würde. Damit würden Konstruktionen möglich, die im ersten Moment eher an Java als an Ruby erinnern, aber offenbar auf einen praktischen Bedarf treffen. So könnte zukünftiger Ruby-Code so aussehen wie in Listing 2 (restricted sei eine selbst geschriebene Metamethode, die ein Array von Methodensymbolen als Argument verlangt).

class Foo
  public  attr :color
  private attr :counter

  private def increment
    # ...
  end

  restricted private def grow
    # ...
  end
end

Langzeitziele für Ruby 3

Mittlerweile haben auch die Langzeitziele für Ruby 3, mit dessen Veröffentlichung etwa 2020 zu rechnen ist, Gestalt angenommen. So hat Yukihiro „Matz“ Matsumoto, der Erfinder und Hauptentwickler von Ruby, auf der RubyKaigi 2016 offiziell das Ziel „Ruby 3×3“ ausgegeben. Das Schlagwort hat er mit zwei Bedeutungen unterlegt. Zum einen soll Ruby 3.0 drei Mal so schnell sein wie Ruby 2.0, zum anderen gibt es drei größere Ziele für Ruby 3:

  1. Performanz
  2. Ein neues Nebenläufigkeitsmodell mit echter Parallelität
  3. Ein unauffälliges statisches Typensystem

Die Hauptziele

Performanz ist das wichtigste Ziel für Ruby 3. Mit Einbindung des JIT-Compilers in Ruby 2.6 ist ein gewaltiger Schritt dahin getan worden, auch wenn weitere Optimierungen erst noch geplant sind. Eine weitere maßgebliche Änderung wird es darüber hinaus bei Strings geben. Schon seit Ruby 2.3 ist es möglich, mithilfe des magischen Kommentars frozen_string_literal: true String-Literale einzufrieren. Ruby 3 wird den Ansatz konsequent fortführen und String-Literale generell einfrieren. Die Details hierzu sind noch umstritten; so könnte es etwa noch zu einer Einführung eines neuen Literals %y oder einer unären Plusmethode für Strings kommen, die ausnahmsweise doch veränderlich sein sollen. Daneben wird Ruby derzeit an diversen kleineren Stellen optimiert, die für sich genommen nicht viel ausmachen, aber in der Summe eine positive Gesamtwirkung herbeiführen dürften.

Angular Kickstart: von 0 auf 100

mit Christian Liebel (Thinktecture AG) und Peter Müller (Freelancer)

JavaScript für Softwareentwickler – für Einsteiger und Umsteiger

mit Yara Mayer (evia) und Sebastian Springer (MaibornWolff)

Das Spannendste neue Feature von Ruby 3 könnte aber die Einführung eines neuen Nebenläufigkeitsmodells, der sogenannten Gilden (Guilds), werden. Über den aktuellen Stand ist leider nicht viel mehr bekannt, als dass eines der Core-Team-Mitglieder, Koichi Sasada, außerhalb des offiziellen Ruby Repositories an ihnen arbeitet und die Grundgedanken auf der RubyKaigi 2016 vorgestellt hat. Danach handelt es sich um einen auf den bekannten Threads und Fibers aufbauenden, aber stärker abstrahierenden Ansatz. Nebenläufige Programmierung ist mit Threads sehr aufwendig und unangenehm, weil man darauf achten muss, auf welche Objekte von mehreren Threads aus zugegriffen wird und man ggf. Synchronisations- und Sperrmechanismen manuell einsetzen muss. Gilden sind dagegen ganz im Geiste der Ruby-Philosophie auf eine möglichst intuitive Programmierung ausgelegt. Dieses Ziel wird erreicht, indem der Zugriff auf ein veränderliches Objekt von mehreren Gilden verboten wird. Soll eine andere Gilde auf ein Objekt zugreifen können, so muss es über einen Kanal (Channel) kopiert oder transferiert werden. In letzterem Fall verliert die ursprüngliche Gilde den Zugriff. Ein Beispiel ist in Listing 3 zu sehen.

guild = Guild.new(script: %q{
  ch = Guild.default_channel
  str = ch.receive # Blockt
})

str = "foo"
guild.transfer_membership(str)
puts str #=> Error

Nach dem Aufruf von #transfer_membership hat das Objekt str die implizite Hauptgilde verlassen und befindet sich nunmehr in der Gilde guild; nur diese hat noch Zugriff. Das Konzept erinnert an die Channels der Programmiersprache Go und befreit den Programmierer vom ausdrücklichen Festlegen von Locks. Der Nettoeffekt der Sache ist, dass es möglich wird, den Code in verschiedenen Gilden endlich echt parallel auszuführen. Nur Code innerhalb einer Gilde wird weiterhin den bekannten, durch den Global VM Lock auferlegten Beschränkungen unterliegen.

Im Hinblick auf das dritte Hauptziel hat Matz einem echten statischen Typensystem eine deutliche Absage erteilt. Syntaktische Änderungen wird es deshalb wohl nicht geben. Insgesamt scheint diese Idee sich noch in einem so frühen Stadium zu befinden, dass sich wenig Greifbares sagen lässt. Es gibt einen von Soutaro Matsumoto auf der RubyKaigi 2017 vorgestellten Vorschlag, der darauf basiert, die Typspezifikationen in Kommentaren im Quelltext sowie in gesonderten Dateien ähnlich den aus C/C++ bekannten Headerdateien zu erstellen. Das folgende Beispiel ist aus Matsumotos Präsentation übernommen; die einzelnen genutzten Elemente werden jeweils statisch typisiert:

# @type var x: String
# @type const Pathname: Pathname.class
path = Pathname.new(x)
# Typ der lokalen Variable "path" wird erraten

Matz hat sich allerdings offenbar nicht sonderlich überzeugt gezeigt. Es erscheint daher verfrüht, in diesem noch sehr vagen Stadium Annahmen über das zukünftige Typensystem zu machen.

Sonstiges

Ruby 3 wird aller Voraussicht nach eine ganze Reihe alter Zöpfe abschneiden. So ist es wahrscheinlich, dass die kaum benutzte sichere Ausführung mithilfe von $SAFE mangels Bedarf entfernt wird. Ebenfalls entfallen wird wohl das bereits seit 2011 als veraltet markierte #autoload , das sich trotz der großen Probleme, die es in Programmen mit mehreren Threads verursacht, weiterhin einer gewissen Beliebtheit erfreut. Matz scheint hier jedoch ausweislich des Eröffnungskommentars im Tracker eine restriktive Linie zu verfolgen, da #autoload offenbar eine erhebliche Komplikation für den Lademechanismus darstellt, die nicht zu beheben ist. Über das Schicksal der meist als Kuriosität behandelten Flipflops ist noch nicht entschieden. Unwahrscheinlich ist dagegen, dass sich der Wiedergänger der schon im Vorfeld von Ruby 1.9 erhobenen Forderung, Symbole und Strings anzugleichen, durchsetzt.

Im Gespräch ist aber die Änderung der berüchtigten lexikalischen Auflösung von Konstanten. So führt etwa der Code aus Listing 4 regelmäßig zu Verwirrung.

module A
  X = 3
  module B
    puts X #=> 3
  end
end

module A::B
  puts X #=> NameError (!)
end

Weil Ruby Konstanten lexikalisch (also nach der syntaktischen Schachtelung) und nicht wie insbesondere Methodenaufrufe dynamisch (nach dem Ausführungskontext) auflöst, entsteht im zweiten Beispiel ein NameError statt der erwarteten Ausgabe „3“.

Schließlich wird sich wohl eine Änderung im Bezug auf den rescue Modifier ergeben. Dieser kann derzeit wie folgt eingesetzt werden:

do_something rescue puts("Problem")

Das angehängte rescue fängt (fast) alle Exceptions, was fast immer zu viel ist; schon ein Tippfehler im Code, der normalerweise einen NoMethodError auslösen würde, wird geschluckt. Es gibt deshalb Stimmen, die die ersatzlose Abschaffung fordern. Andererseits gibt es etwa vom Autor dieses Artikels den Vorschlag, den rescue Modifier um die Angabe der zu fangenden Exception-Klasse zu erweitern.

Fazit

Mit Ruby 2.5 ist Ruby um eine hilfreiche neue Kurzsyntax für rescue-Ausdrücke in Blöcken und eine überaus praktische Erweiterung unter geschickter Ausnutzung der case-Gleichheit bereichert worden. Ruby 2.6 wird durch Einbindung des neuen JIT-Compilers eine drastische Performanzverbesserung erfahren und es ermöglichen, durch Array#to_proc die beliebte &-Schreibweise auch bei Methoden mit Argumenten zu nutzen. Performanz ist auch das Hauptziel von „Ruby 3×3“, wie Matz es nennt, neben der mit Spannung zu erwartenden Einführung von Gilden und einem noch vagen, aber nicht minder interessanten statischen Typensystem.

Entwickler Magazin

Entwickler Magazin abonnierenDieser Artikel ist im Entwickler Magazin erschienen.

Natürlich können Sie das Entwickler Magazin über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. In unserem Shop ist das Entwickler Magazin ferner im Abonnement oder als Einzelheft erhältlich.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu:
X
- Gib Deinen Standort ein -
- or -