to_s vs. to_str (y to_i / to_a / to_h vs. to_int / to_ary / to_hash) en Ruby

Estoy aprendiendo Ruby y he visto un par de métodos que me confunden un poco, particularmente to_s vs to_str (y de manera similar, to_i / to_int , to_a / to_ary , y to_h / to_hash ). Lo que he leído explica que los formularios más cortos (por ejemplo, to_s ) son para las conversiones explícitas, mientras que los más largos son para las conversiones implícitas.

Realmente no entiendo cómo to_str realmente sería utilizado. ¿Alguna otra cosa que no sea una cadena definirá to_str ? ¿Puedes dar una aplicación práctica para este método?

Nótese primero que todo esto se aplica a cada par de métodos de coerción “cortos” (por ej. to_s / to_i / to_a / to_h ) vs. “long” (por ej. to_str / to_int / to_ary / to_hash ) en Ruby (para sus respectivos tipos) como todos ellos tienen la misma semántica.


Ellos tienen diferentes significados. No debe implementar to_str menos que su objeto actúe como una cadena, en lugar de solo ser representable por una cadena. La única clase principal que implementa to_str es String.

De Programming Ruby (citado de esta publicación de blog , que vale la pena leer todo):

[ to_i y to_s ] no son particularmente estrictos: si un objeto tiene algún tipo de representación decente como cadena, por ejemplo, probablemente tenga un método to_s … [ to_int y to_str ] son ​​funciones de conversión estrictas: las implementa solo si [ su] objeto puede usarse de forma natural en cualquier lugar donde se pueda usar una cuerda o un número entero.

La documentación anterior de Ruby de la Pico tiene esto que decir:

A diferencia de to_s , que es soportado por casi todas las clases, to_str normalmente se implementa solo por aquellas clases que actúan como cadenas.

Por ejemplo, además de Integer , tanto Float como Numeric implementan to_int (el equivalente de to_str de to_str ) porque ambos pueden sustituir fácilmente a un entero (todos son realmente números). A menos que su clase tenga una relación similar estrecha con String, no debe implementar to_str .

Para saber si debe usar / implementar to_s / to_str , veamos algunos ejemplos. Es revelador considerar cuándo fallan estos métodos .

 1.to_s # returns "1" Object.new.to_s # returns "#" 1.to_str # raises NoMethodError Object.new.to_str # raises NoMethodError 

Como podemos ver, to_s está feliz de convertir cualquier objeto en una cadena. Por otro lado, to_str provoca un error cuando su parámetro no se parece a una cadena.


Ahora echemos un vistazo a Array#join .

 [1,2].join(',') # returns "1,2" [1,2].join(3) # fails, the argument does not look like a valid separator. 

Es útil que Array#join convierta para encadenar los elementos de la matriz (lo que sea que realmente sean) antes de unirlos, por lo que Array#join llamadas to_s en ellos.

Sin embargo, se supone que el separador es una cadena; es probable que alguien que llama [1,2].join(3) esté cometiendo un error . Esta es la razón por la que Array#join llamadas to_str en el separador.


El mismo principio parece ser válido para los otros métodos. Considere to_a / to_ary en un hash:

 {1,2}.to_a # returns [[1, 2]], an array that describes the hash {1,2}.to_ary # fails, because a hash is not really an array. 

En resumen, así es como lo veo:

  • llame a to_s para obtener una cadena que describa el objeto.
  • llama to_str para verificar que un objeto realmente actúa como una cadena.
  • implemente to_s cuando pueda construir una cadena que describa su objeto.
  • Implementa to_str cuando tu objeto puede comportarse completamente como una cadena.

Creo que un caso en el que podrías implementar to_str es quizás una clase ColoredString , una cadena que tiene un color adjunto. Si te parece claro que pasar una coma de color para join no es un error y debería dar como resultado "1,2" (aunque esa cadena no se coloreará), entonces implementa to_str en ColoredString.