Geschrieben von mame am 12.12.2019
Übersetzt von Marvin Gülker
Diese Artikel erklärt den geplanten Kompatibilitätsbruch bei Schlüsselwortargumenten in Ruby 3.0.
Kurzfassung
Ab Ruby 3.0 werden Positions- und Schlüsselwortargumente getrennt. Ruby 2.7 warnt bei Verhalten, das für Ruby 3.0 geändert werden muss. Wenn Sie eine der folgenden Warnungen sehen, dann müssen Sie Ihren Code ändern:
Using the last argument as keyword parameters is deprecated
, oderPassing the keyword argument as the last hash parameter is deprecated
, oderSplitting the last argument into positional and keyword parameters is deprecated
In den meisten Fällen können Sie die Inkompatibilität durch Einfügen
des doppelten Auflösungsoperators (double splat operator)
vermeiden. Dieser verlangt die ausdrückliche Übergabe von
Schlüsselwortargumente anstelle eines Hash. Dementsprechend können
geschweifte Klammern {}
erforderlich sein, um ausdrücklich ein
Hash
-Objekt statt Schlüsselwortargumenten zu verlangen. Lesen Sie
den nachfolgenden Abschnitt “Typische Fälle” für weitere
Informationen.
Ab Ruby 3 muss eine Methode, die alle ihre Argumente delegiert, dies
neben den Positions- auch ausdrücklich für Schlüsselwortargumente tun.
Wenn Sie das Delegationsverhalten von Ruby 2.7 beibehalten wollen,
müssen Sie ruby2_keywords
verwenden. Siehe den Abschnitt “Behandlung
der Argument-Delegation” weiter unten.
Typische Fälle
Dieses ist der typischste Fall. Sie können den doppelten
Auflösungsoperator (**
) benutzen, um Schlüsselwörter statt eines
Hash zu übergeben.
Hier ein anderer Fall. Sie können geschweifte Klammern ({}
)
verwenden, um ausdrücklich ein Hash anstelle von Schlüsselwörtern zu
übergeben.
Was veraltet nun?
In Ruby 2 können Schlüsselwortargumente als letztes positionsgebundenes Argument behandelt werden und umgekehrt ein letztes positionsgebundenes Hash-Argument als Schlüsselwortargumente.
Wie im letzten Abschnitt beschrieben wird, ist diese diese
automatische Konvertierung manchmal zu komplex und problematisch.
Daher gilt sie in Ruby 2.7 nun als veraltet und wird in Ruby 3
entfernt. Das bedeutet, dass Schlüsselwortargumente in Ruby 3
vollständig von den positionsgebundenen Argumenten getrennt sein
werden. Wenn Sie also Schlüsselwortargumente übergeben wollen, dann
sollten Sie stets foo(k: expr)
oder foo(**expr)
benützen. Wenn Sie
Schlüsselwortargumente akzeptieren wollen, sollten Sie stets
def foo(k: default)
oder def foo(k:)
oder def foo(**kwargs)
verwenden.
Beachten Sie, dass Ruby 3 sich beim Aufruf einer Methode mit Schlüsselwortargumenten auch dann nicht anders verhält, wenn die Methode keine Schlüsselwortargumente akzeptiert. Beispielsweise wird der folgende Fall nicht als veraltet markiert und wird auch in Ruby 3.0 weiterhin funktionieren. Die Schlüsselwortargumente werden weiterhin als positionsgebundenes Hash behandelt.
Dies findet seinen Grund darin, dass dieses Idiom sehr häufig genutzt wird und gleichzeitig die beabsichtigte Verwendung des Arguments nicht mehrdeutig ist. Ein Verbot dieser Konvertierung würde eine zusätzliche Inkompatibilität für nur wenig Nutzen einführen.
Dennoch wird dieser Stil für neuen Code nicht mehr empfohlen, es sei denn, Sie übergeben oft Hashes als positionsgebundene Argumente und benützen außerdem Schlüsselwortargumente. Ansonsten verwenden Sie den doppelten Auflösungsoperator:
Wird mein Code mit Ruby 2.7 nicht mehr funktionieren?
Eine kurze Antwort ist „vielleicht nicht“.
Die Änderungen in Ruby 2.7 sind als Migrationspfad zu Ruby 3.0 gedacht. Prinzipiell warnt Ruby 2.7 lediglich wegen Verhaltens, das sich in Ruby 3 ändern wird. Jedoch enthält es einige inkompatible Änderungen, die wir als hinnehmbar betrachten. Siehe den Abschnitt „Andere kleine Änderungen“ für Details.
Abgesehen von den Warnungen und kleineren Änderungen versucht Ruby 2.7 die Kompatibilität mit Ruby 2.6 aufrecht zu erhalten. Daher wird Ihr Code wahrscheinlich mit Ruby 2.7 weiterhin funktionieren und nur Warnungen auslösen. Indem Sie ihn auf Ruby 2.7 ausführen, können Sie daher prüfen, ob Ihr Code mit Ruby 3.0 kompatibel ist.
Wenn Sie die Veraltungswarnungen unterdrücken wollen, verwenden Sie
den Kommandozeilenschalter -W:no-deprecated
oder fügen Sie Ihrem
Code den Befehl Warning[:deprecated] = false
hinzu.
Weiterleiten von Argumenten
Ruby 2.6 und früher
In Ruby 2 können Sie eine Delegatormethode schreiben, indem Sie ein
*rest
- und ein &block
-Argument akzeptieren und beide an die
Zielmethode weitergeben. In diesem Verhalten enthalten ist die
automatische Konvertierung zwischen positionsgebundenen und
Schlüsselwortargumenten.
Ruby 3
Sie müssen Schlüsselwortargumente ausdrücklich delegieren.
Alternativ können Sie, wenn Sie keine Kompatibilität mit Ruby 2.6 oder
früher benötigen und die Argumente nicht verändern, die mit Ruby 2.7 neu
eingeführte Delegatorsyntax (...
) verwenden.
Ruby 2.7
In Kürze: verwenden Sie Module#ruby2_keywords
und delegieren Sie *args, &block
.
ruby2_keywords
akzeptiert Schlüsselwortargumente als das letzte
Hash-Argument und leitet sie beim Aufruf der Zielmethode als
Schlüsselwortargumente weiter.
Tatsächlich erlaubt Ruby 2.7 den neuen Delegationsstil in vielen Fällen. Es gibt allerdings einen Sonderfall; siehe den nächsten Abschnitt.
Eine mit Ruby 2.6, 2.7 und Ruby 3 kompatible Delegation
In Kürze: verwenden Sie auch hier Module#ruby2_keywords
.
Leider muss man hier den alten Delegationsstil (also kein **kwargs
)
verwenden, weil Ruby 2.6 und früher die neue Delegationssyntax nicht
richtig verarbeiten. Das ist einer der Gründe für die Abspaltung von
Schlüsselwortargumenten; die Details erklärt der letzte Abschnitt.
ruby2_keywords
erlaubt es, den alten Stil noch in Ruby 2.7 und sogar
in Ruby 3.0 zu benutzen. Weil ruby2_keywords
freilich in Ruby 2.6
und früher fehlt, können Sie hierfür das ruby2_keywords-Gem
einsetzen oder die Methode selbst definieren:
Wenn Ihr Code nciht mit Ruby 2.6 oder älter kompatibel sein muss, können Sie den mit Ruby 2.7 neu eingeführten Weg einschlagen. Das funktioniert in fast allen Fällen. Einen unglücklichen Sonderfall gibt es allerdings:
Ein leeres Hash wird automatisch konvertiert und von **kwargs
absorbiert, und die Delegation entfernt dann das leere
Schlüsselwort-Hash, sodass an target
gar kein Argument übergeben
wird. Soweit wir wissen, handelt es sich hierbei um den einzigen
Sonderfall.
Wie in der letzten Zeile festgehalten, können Sie das Problem durch
Übergabe von **{}
umgehen.
Wenn Sie sich wirklich Sorgen um die Portabilität machen, verwenden
Sie ruby2_keywords
. (Es sei daran erinnert, dass Ruby 2.6 und früher
ihrerseits dutzende Problemfälle bei der Behandlung von
Schlüsselwortargumenten aufwiesen :-)
Eventuell wird ruby2_keywords
mit dem Ende der Unterstützung von
Ruby 2.6 entfernt werden. In diesem Fall werden wir empfehlen,
Schlüsselwortargumente ausdrücklich zu delegieren (siehe oben den Code
für Ruby 3).
Andere kleine Änderungen
Es gibt in Ruby 2.7 drei kleinere Änderungen an den Schlüsselwortargumenten.
1. Nichtsymbole sind als Schlüssel zulässig
In Ruby 2.6 und früher waren nur Symbolschlüssel zulässig. Ab Ruby 2.7 können auch andere Objekte als Schlüssel eingesetzt werden.
Wenn eine Methode sowohl optionale als auch Schlüsselwortargumente akzeptiert, wurde in Ruby 2.6 ein Hash-Objekt, das sowohl Symbol- als auch andere Schlüssel aufwies, in zwei Hashes zerlegt. Ab Ruby 2.7 werden beide Arten als Schlüsselwortargumente erkannt, weil Nichtsymbolschlüssel erlaubt sind.
Ruby 2.7 zerlegt Hashes mit einer Warnung, wenn ein Hash oder
Schlüsselwortargumente, die sowohl Symbol- als auch
Nichtsymbolschlüssel enthalten, an eine Methode übergeben werden, die
ausdrückliche Schlüsselwortargumente, aber kein
Schlüsselwortrestargument (**kwargs
) akzeptiert. Dieses Verhalten
wird in Ruby 3 entfernt werden und einen ArgumentError
auslösen.
2. Doppelter Auflösunsoperator mit leerem Hash (**{}
) übergibt keine Argumente
Bis Ruby 2.6 führte die Übergabe von **leeres_hash
zur Übergabe
eines leeren Hashes als positionsgebundenes Argument. Ab Ruby 2.7
werden gar keine Argumente übergeben.
Beachten Sie, dass foo(**{})
weder in Ruby 2.6 noch in 2.7
irgendetwas übergibt. Bis Ruby 2.6 wird **{}
vom Parser entfernt,
und ab Ruby 2.7 wird es genauso behandelt wie **leeres_hash
, wodurch
eine einfache Möglchkeit geschaffen wird, gar keine
Schlüsselwortargumente an eine Methode zu übergeben.
In Ruby 2.7 führt der Aufruf einer Methode mit zu wenigen
erforderlichen positionsgebundenden Argumenten und
foo(**leeres_hash)
dazu, dass ein leeres Hash übergeben und eine
Warnung ausgegeben wird, um Kompatibilität mit Ruby 2.6 zu
gewährleisten. Dieses Verhalten wird in 3.0 entfernt.
3. Keine-Argumente-Syntax (**nil
) wird eingeführt
Sie können jetzt **nil
in einer Methodendefinition verwenden, um
ausdrücklich klarzustellen, dass die Methode keine
Schlüsselwortargumente akzeptiert. Der Aufruf solcher Methoden mit
Schlüsselwortargumenten verursacht einen ArgumentError
. (Hierbei
handelt es sich um ein neues Feature, nicht um eine Inkompatibilität.)
Das ist nützlich, um festzulegen, dass die Methode keine Schlüsselwortargumente akzeptiert. Ansonsten würden im obigen Beispiel die Schlüsselwortargumente in das Restargument absorbiert. Wenn Sie eine Methode dann später mit Schlüsselwortargumenten erweitern wollen, könnte sie die folgende Inkompatibilität aufweisen:
Warum wir die automatische Konvertierung entfernen wollen
Ursprünglich erschien die automatische Konvertierung als eine gute Idee und funktionierte in vielen Fällen gut. Es stellte sich aber heraus, dass zu viele unerwartete Fälle vorhanden waren, die zu zahlreichen Bugreports über das vermeintlich fehlerhafte Verhalten geführt haben.
So funktioniert die automatische Konvertierung nicht richtig, wenn eine Methode optionale positionsgebundene Argumente und gleichzeitig Schlüsselwortargumente akzeptiert. Einige Leute erwarten in diesem Fall, dass das letzte Hash-Objekt als positionsgebundenes Argument behandelt wird, während andere davon ausgehen, es werde zu Schlüsselwortargumenten konvertiert.
Hier ist ein Beispiel für einen der besonders verwirrenden Fälle:
In Ruby 2 übergibt foo({})
ein leeres Hash als normales Argument
(d.h., {}
wird an x
zugewiesen), während bar({})
ein
Schlüsselwortargument übergibt (d.h. {}
wird an kwargs
zugewiesen). Die bloße Angabe jede_methode({})
ist mehrdeutig.
Man kann bar({}, **{})
verstehen als das Verlangen, an x
ausdrücklich das leere Hash zuzuweisen. Überraschenderweise
funktioniert das nicht wie erwartet; in Ruby 2.6 gibt das immer noch
[1, {}]
aus. Das liegt daran, dass **{}
in Ruby 2.6 schon vom
Parser ignoriert wird und das dann einzige Argument {}
automatisch
in ein Schlüsselwortargument (**kwargs
) konvertiert wird. Es ist in
diesem Fall nötig, den Aufruf als bar({}, {})
zu fassen, was
überaus anstößig ist.
Dieselben Probleme bestehen bei Methoden, die sowohl Rest- als auch Schlüsselwortargumente akzeptieren. Die ausdrückliche Delegation von Schlüsselwortargumenten schlägt dann fehl.
foo()
übergibt keine Argumente, aber target
wird in Ruby 2.6 ein
leeres Hash-Argument zugewiesen. Grund dafür ist, dass die Methode
foo
ihre Schlüsselwortargumente (**kwargs
) ausdrücklich delegiert.
Wenn foo()
aufgerufen wird, ist args
ein leeres Array, kwargs
ist ein leeres Hash, und block
ist nil
. Der Aufruf
target(*args, **kwargs, &block)
übergibt dann ein leeres Hash als
Argument, weil **kwargs
automatisch in ein positionsgebundenes
Hash-Argument konvertiert wird.
Schließlich verursacht die automatische Konvertierung nicht nur Verwirrung, sondern schränkt die Erweiterbarkeit von Methoden ein. Weitere Details über die Gründe der Änderung und bestimmte Entscheidungen für die Implementation finden sich in [Feature #14183].
Hinweis
Dieser Artikel wurde freundlicherweise von Jeremy Evans und Benoit Daloze überprüft (wenn nicht sogar mitgeschrieben).
Historie
- Aktualisiert 2019-12-25: In 2.7.0-rc2 wurden die Warnungen leicht verändert und ein API zu ihrer Unterdrückung hinzugefügt.