Publicado por mame el 2019-12-12
Traducción de vtamara
Este artículo explica la incompatibilidad planeada de los argumentos de palabra clave en Ruby 3.0
tl;dr
En Ruby 3.0 los argumentos posicionales y los argumentos de palabra clave serán separados. Ruby 2.7 advertirá comportamientos que cambiarán en Ruby 3.0. Si ve las siguientes advertencias, debe actualizar su código:
- Usar el último argumento como un parámetro de palabra clave es obsoleto, o
- Pasar al argumento de palabra clave como último parámetro de diccionario (has) es obsoleto, o
- Dividir el último argumento en parámetros posicional y de palabra clave es obsoleto
En la mayoría de casos, puede evitar la incompatibilidad agregando
el operador doble splat. Que especifica que se pasa un argumento
de palabra clave en lugar de un objeto Hash
. De la misma forma,
puede agregar llaves {}
para pasar explícitamente un
objeto Hash
, en lugar de argumentos de palabra clave. Lea la sección
“Casos típicos” más adelante para ver detalles.
En Ruby 3, un método que delega todos los argumentos debe delegar
explícitamente argumentos de palabra clave además de los argumentos
posicionales. Si quiere mantener el comportamiento de delegación
de Ruby 2.7 y anteriores, use ruby2_keywords
.
Vea más detalles en la sección “Manejando la delegación de argumentos.”
Casos típicos
Aquí están los casos más típicos. Puede usar un operador doble splat
(**
) para pasar palabras clave en lugar de un diccionario.
Aquí hay otro caso. Puede usar llaves ({}
) para pasar un diccionario
en lugar de palabras claves explícitas.
¿Qué se ha vuelto obsoleto?
En Ruby 2, los argumentos de palabra clave puede tratarse como el último argumento Hash posicional y un último argumento Hash posicional puede tratarse como un argumento de palabra clave.
Como la conversión automática es en ocasiones demasiado compleja y
problemática, como se explica al final de la sección, se ha despreciado
en Ruby 2.7 y se eliminará en Ruby 3. En otras palabras, los argumentos
de palabra clave se separarán por completa de los posicionales en
Ruby 3. Así que cuando quiera pasar argumento de palabra clave,
debe usar siempre foo(k: expr)
o foo(**expr)
.
Si desea aceptar argumentos de palabra clave, en principio debe usar
siempre def foo(k: default)
o def foo(k:)
o
def foo(**kwargs)
.
Note que Ruby 3.0 no se comporta de manera diferente cuando se llama un método que no acepta argumentos de palabra clave con argumentos de palabra clave. Por ejemplo, el siguiente caso no va a ser obsoleto y seguirá operando en Ruby 3.0. Los argumentos de palabra clave sigue tratándose como argumentos Hash posicionales.
Esto es porque el tal estilo se usa con mucha frecuencia, y no hay ambigüedad en la forma como deben tratarse los argumentos. Prohibir esta conversión resultaría en incompatibilidad adicional con poco beneficio.
Sin embargo, no se recomienda este estilo en código nuevo, a menos que suela pasar un Has como argumento posicional, y también esté usando argumentos de palabra clave. En otro caso, use un doble splat:
¿Mí código se quebrará en Ruby 2.7?
La respuesta corta es “posiblemente no”.
Los cambios en Ruby 2.7 se diseñaron como ruta de migración hacía 3.0. Mientras que en principio Ruby 2.7 solo advertirá sobre comportamientos que cambiarán en Ruby 3, incluye algunos cambios incompatibles que consideramos menores. Ver detalles en la sección “Otros cambios menores.”
Excepto por las advertencias y cambios menores, Ruby 2.7 trata de mantener compatibilidad con Ruby 2.6. Así, que su código probablemente operará en Ruby 2.7, aunque emita algunas advertencia. Y al correrlo en Ruby 2.7, puede verificar que su código esté listo para Ruby 3.0
Si desea deshabilitar los mensajes de obsolescencia, por favor use el
argumento de la línea de ordenes -W:no-deprecated
o añada a
su código Warning[:deprecated] = false
.
Manejando la delegación de argumentos
Ruby 2.6 o anteriores
En Ruby 2, puede escribir una delegación de un método aceptando un
argumento *rest
y un argumento &block
, y pasando ambos al
método objetivo.
De esta manera, los argumentos de palabra clave también
se manejan de manera implícita durante la conversión automática
entre argumentos posicionales y argumentos de palabra clave.
Ruby 3
Necesita delegar explícitamente los argumntos de palabra clave.
De manera alterna, si no necesita compatibilidad con Ruby 2.6 o anteriores
y no altera ningún argumento, puede usar la nueva sintaxis para delegar
(...
) que se introduce en Ruby 2.7.
Ruby 2.7
Brevemente : use Module#ruby2_keywords
y delege *args, &block
.
ruby2_keywords
acepta argumentos de palabra clave como
el último argumento Hash, y lo pasa como argumento de palabra clave
cuando llama al otro método.
De hecho, Ruby 2.7 permite el nuevo estilo de delegación en muchos casos. Sin embargo, hay un caso esquina conocido. Ver la siguiente sección.
Una delegación compatible que funciona en Ruby 2.6, 2.7 y Ruby 3
Brevemente: nuevamente use Module#ruby2_keywords
Infortunadamente, necesitamos usar la delegación en el estilo antiguo
(i.e., sin **kwargs
) porque Ruby 2.6 o anteriores no maneja el nuevo
estilo de delegación correctamente.
Esta es una de las razones para la separación de argumentos de palabras
clave; los detalles se describen en la sección final. Y ruby2_keywords
le permite emplear el estilo antiguo incluso en Ruby 2.7 y Ruby 3.0.
Como no hay ruby2_keywords
definido en 2.6 o anterior, por favor use
la gema ruby2_keywords
od definalo usted mismo:
Si su código no necesita ejecutarse con Ruby 2.6 o anteriores, puede intentar el nuevo estilo de Ruby 2.7. En casi todos los casos, funciona. Note que, sin embargo, hay algunos casos esquina como los siguientes:
Un argumento Hash vacío se convierte automáticamente y es absorbido en
**kwargs
, y la llamada de delegación elimina la palabra clave vacía
del hash, así que no se pasa argumento alguno a target
.
Hasta donde sabemos, este es el único caso esquina.
Como se hace notar en la última línea, usted puede sobrellevar este
problema usando **{}
.
Si realmente se preocupa por la portabilidad, use ruby2_keywords
.
(Reconociendo que Ruby 2.6 y anteriores tienen muchos casos
esquinas en los argumentos de palabras. :-)
ruby2_keywords
puede ser eliminado en el futuro después de que
Ruby 2.6 alcance su fin-de-vida. En ese momento, recomendamos delegar
argumentos de palabra reservada (ver el código para Ruby 3 antes presentado).
Otros cambios menores
Hay otros cambios menores respecto a argumentos de palabra clave en Ruby 2.7.
1. Llaves que no son símbolos se permite como argumentos de palabra clave
En Ruby 2.6 o anteriores, sólo llaves que eran símbolos se permitían en argumentos de palabra clave. En Ruby 2.7, los argumentos de palabra clave pueden usar llaves que no son símbolos.
Si un método acepta tanto argumentos opcionales como de palabra clave, el objeto Hash que tenga llaves símbolos y otras que no sean símbolos se dividirá en dos en Ruby 2.6. En Ruby 2.7 ampos se acepta como palabras clave porque las llaves que no son símbolos son permitidas.
Ruby 2.7 aún divide diccionarios con una advertencia si se pasa un Hash
o argumentos de palabras clave tanto con llaves que sean símbolos como
con llaves que no sean símbolos a un método que acepte explícitamente
palabras clave pero no el argumento de palabra clave para el resto
(**kwargs
). Este comportamiento será eliminado en
Ruby 3, y se lanzará un ArgumentError
.
2. Doble splat con un diccionario vacío (**{}
) no pasa argumentos
En Ruby 2.6 y anteriores, al pasar **empty_hash
se pasa un
Hash vacío como argumento posicional. En Ruby 2.7 o posteriores,
no pasa argumento alguno.
Note que foo(**{})
no pasa dato alguno ni en Ruby 2.6 ni en
2.7. En Ruby 2.6 y anteriores, **{}
es eliminado por el analizador
sintáctico, y en Ruby 2.7 y posteriores, se trata igual que
**empty_hash
, permitiendo no pasar argumentos de palabra clave
de manera sencilla a un método.
En Ruby 2.7 cuando se llama un método con una cantidad insuficiente
de argumentos posicionales, foo(**empty_hash)
pasa un
diccionario vacío y emite una advertencia, por compatibilidad con
Ruby 2.6. Este comportamiento será eliminado en 3.0
3. Se introduce sintaxis sin-argumentos-de-palabra-clave (**nil
)
Puede usar **nil
en la definición de un método para marcar de manera
explícita que el método no acepta argumentos de palabra clave.
Llamar tales métodos con argumentos de palabra clave resultará en un
ArgumentError
. (En realidad es una nueva característica y no
una incompatibilidad)
Esto es útil para hacer explícito que el método no acepta argumentos de palabra clave. De lo contrario los argumentos de palabra clave son absorbidos en el argumento para el resto en el ejemplo anterior. Si extiende un método para que acepte argumentos de palabra clave, el método podría tener incompatibilidades así:
Razones para despreciar la conversión automática
La conversión automática inicialmente parecía una buena idea, y funcionó bien en muchos casos. Sin embargo, tenía muchos casos esquina, y hemos recibidos muchos reportes de fallas por el comportamiento.
La conversión automática no funciona bien cuando un método acepta argumentos posicionales opciones y argumentos de palabra clave. Alguna gente espera que el último objeto Hash sea tratado como un argumento posicional, y otra espera que sea convertido en argumento de palabra clave.
Este es uno de los casos más confusos:
En Ruby 2, foo({})
pasa un diccionario vacío como un argumento normal
(i.e., {}
se asigna a x
), mientras que
bar({})
pasa un argumento de palabra clave (i.e, {}
se asigna a kwargs
).
Así que any_method({})
es muy ambiguo.
Puede pensar que bar({}, **{})
pasa el diccionario vacío a x
d
de forma explícita.
Pero sorprendentemente, no opera como espera; aún imprime [1, {}]
en Ruby 2.6.
Esto es porque **{}
es ignorado por el analizador en Ruby 2.6, y
el primer argumento {}
se convierte automáticamente a
palabras clave (**kwargs
). En tal caso, necesita llamar
bar({}, {})
, que resulta muy extraño.
El mismo problema ocurre con métodos que aceptan argumentos de palabra clave y resto. Esto hace que no opere la delegación explícita de argumentos de palabra clave.
foo()
no pasa argumentos, pero target
recibe un argumento de diccionario
vacío en Ruby 2.6. Esto es porque el método foo
delega las
palabras clave (**kwargs
) explícitamente. Cuando se llama foo()
,
args
es un arreglo vacío, kwargs
es un Hash vacío y block
es nil
.
Y entonces target(*args, **kwargs, &block)
pasa un Hash vacío
como argumento porque **kwargs
se convierte automáticamente a
un argumento Hash posicional.
La conversión automática no sólo confunde a la gente sino que hace el método menos extensible. Ver más detalles en la [Característica #14183] y razones para el cambio de comportamiento, y porque se hicieron algunas elecciones de implementación.
Agradecimientos
Este artículo fue revisado amablemente (e incluso son co-autores) por Jeremy Evans y Benoit Daloze.
Historia
- Actualizado 2019-12-25: En 2.7.0-rc2, el mensaje de advertencia fue cambiado levemente, y se agregó un API para eliminar la advertencia.