To_s против to_str (и to_i/to_a/to_h против to_int/to_ary/to_hash) в Ruby

Я изучаю Ruby, и я видел несколько методов, которые меня немного смущают, особенно to_s vs to_str (и аналогичным образом to_i/to_int, to_a/to_ary и to_h/to_hash). То, что я прочитал, объясняет, что более короткая форма (например, to_s) предназначена для явных преобразований, а более длинная - для неявных преобразований.

Я действительно не понимаю, как использовать to_str. Может ли что-то отличное от String определять to_str? Можете ли вы дать практическое применение для этого метода?

Ответ 1

Обратите внимание, что все это относится к каждой паре "коротких" (например, to_s/to_i/to_a/to_h) по сравнению с "длинной" (например, to_str/to_int/to_ary/to_hash) в Ruby (для их соответствующих типов), поскольку все они имеют одинаковую семантику.


У них разные значения. Вы не должны реализовывать to_str, если ваш объект не работает как строка, а не просто представляется в виде строки. Единственным основным классом, который реализует to_str, является сама строка.

Из программирования Ruby (цитируется это сообщение в блоге, которое стоит прочитать все):

[to_i и to_s] не являются особенно строгими: если у объекта есть какое-то достойное представление в виде строки, например, у него, вероятно, будет метод to_s... [to_int и to_str] являются строгими функциями преобразования: вы реализуете их только в том случае, если [ваш] объект может быть естественным образом использован в каждом месте, где может использоваться строка или целое число.

Документация Older Ruby от Pickaxe позволяет:

В отличие от to_s, который поддерживается почти всеми классами, to_str обычно реализуется только теми классами, которые действуют как строки.

Например, помимо Integer, Float и Numeric реализовать to_int (to_i эквивалент to_str), потому что они оба могут легко заменить Integer (все они фактически являются числами). Если ваш класс не имеет тесной связи с String, вы не должны реализовывать to_str.

Ответ 2

Чтобы понять, следует ли использовать/реализовать to_s/to_str, рассмотрим некоторые примеры. Показывается, что , когда этот метод терпит неудачу.

1.to_s              # returns "1"
Object.new.to_s     # returns "#<Object:0x4932990>"
1.to_str            # raises NoMethodError
Object.new.to_str   # raises NoMethodError

Как мы видим, to_s счастлив превратить любой объект в строку. С другой стороны, to_str вызывает ошибку, когда ее параметр не похож на строку.


Теперь посмотрим на Array#join.

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

Полезно, что Array#join преобразует в строку элементы в массиве (независимо от того, что они есть на самом деле) перед их присоединением, поэтому Array#join вызывает to_s на них.

Однако разделитель должен быть строкой - кто-то, вызывающий [1,2].join(3), скорее всего, совершит ошибку. Вот почему Array#join вызывает to_str в разделителе.


Тот же принцип, по-видимому, сохраняется и для других методов. Рассмотрим to_a/to_ary на хеш:

{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.

Вкратце, вот как я это вижу:

  • вызов to_s, чтобы получить строку, описывающую объект.
  • call to_str, чтобы убедиться, что объект действительно действует как строка.
  • реализовать to_s, когда вы можете создать строку, описывающую ваш объект.
  • реализовать to_str, когда ваш объект может полностью вести себя как строка.

Я думаю, что случай, когда вы могли бы реализовать to_str самостоятельно, может быть, это класс ColoredString - строка с прикрепленным к ней цветом. Если вам кажется, что передача цветной запятой на join не является ошибкой и должна привести к "1,2" (даже если эта строка не будет окрашена), тогда реализуйте to_str в ColoredString.