Desempaquetado de argumentos de palabras clave (splat) en Ruby

Lo que está pasando a continuación me parece un poco extraño.

def f(a, b) puts "#{a} :: #{b}" end f(*[1, 2], **{}) # prints "1 :: 2" hash = {} f(*[1, 2], **hash) ArgumentError: wrong number of arguments (3 for 2) f(*[1, 2], **Hash.new) ArgumentError: wrong number of arguments (3 for 2) 

¿Es esta una característica de optimización del comstackdor?

Ese es un error de Ruby que ha sido reportado varias veces (por ejemplo aquí por mi) pero que no se ha corregido.

Supongo que desde que se introdujo la característica de argumento de palabra clave, la syntax de doble splat se ha vuelto turbia, y esa es la causa indirecta de este error. Escuché que Matz está considerando introducir una nueva syntax en alguna versión futura de Ruby para distinguir hashes y argumentos de palabras clave.

[Editar: Vi la respuesta de @ sawa después de completar la mía. Tenía razón: es un error!]

Que se obtengan resultados diferentes cuando un hash vacío literal está duplicado y un hash vacío que es el valor de una variable está duplicado, me parece que es evidencia prima facia de que se debe a un error en Ruby. Para comprender por qué puede existir el error, considere primero la razón para pasar un hash a un método.

Supongamos que definimos un método con algunos argumentos de palabras clave:

 def my_method(x, a: 'cat', b: 'dog') [x, a, b] end my_method(1) #=> [1, "cat", "dog"] 

Los valores predeterminados se aplican a los dos argumentos de palabras clave. Ahora intenta:

 my_method(1, a: 2) #=> [1, 2, "dog"] 

Ahora vamos a usar un hash doble-salpicado.

 h = { a: 2, b: 3 } my_method(1, **h) #=> [1, 2, 3] 

Esto funciona de la misma manera con los argumentos de palabras clave requeridos (Ruby 2.1+).

 def my_method(x, a:, b:) [x, a, b] end my_method(1, **h) #=> [1, 2, 3] 

Sin embargo, para usar un hash duplicado como argumento, el hash no puede contener claves que no estén enumeradas como argumentos en la definición del método.

 def my_method(x, a:) [x, a] end h = { a: 2, b: 3 } my_method(1, **h) #=> ArgumentError: unknown keyword: b 

Por lo tanto, surge la pregunta: ¿se puede pasar un hash vacío a doble espacio como un argumento, considerando que todas las claves de hash (ninguna) se incluyen como argumentos en la definición del método (en cuyo caso no tendría efecto)? Vamos a intentarlo.

 def my_method(x) [x] end my_method(1, **{}) #=> [1] 

¡Sí!

 h = {} my_method(1, **h) #=> ArgumentError: wrong number of arguments (given 2, expected 1) 

¡No!

Eso no tiene sentido. Entonces, asumiendo que esto es un error, ¿cómo podría haber surgido? Sospecho que puede tener que ver con una optimización de Ruby, como lo sugiere el OP. Si el hash vacío es un literal, se podría tratar con él antes en el código de Ruby que si fuera el valor de la variable. Supongo que quien escribió el código anterior respondió “sí” a la pregunta que formulé anteriormente, y quien escribió el último código respondió “no”, o no consideró el caso de un hash vacío en ese momento.

Si esta teoría de errores no se derriba, el OP o alguien más debería informarla.

Tengo la sensación de que está tropezando con una peculiaridad del operador de splat en relación con esa estructura de hash vacía. Parece que la propagación de un hash en línea vacío hace que desaparezca, pero todo lo demás se expande como un argumento de algún tipo.

De hecho, esto puede ser un error en Ruby, aunque no estoy tan sorprendido.

Su función f no acepta argumentos de palabras clave de ningún tipo, por lo que si se realiza un bash lo suficientemente vigoroso para proporcionarlos, fallará. Los dos últimos ejemplos parecen estar tratando de forzar en un hash vacío como un argumento literal.

    Intereting Posts