Это делает мой класс менее гибким / пригодным для использования. Например: я не мог использовать String#+
для объединения вывода Status с другой строкой, если у меня не было Status#to_str
.
Это плохой пример, потому что конкатенация строк - это недиоматический Ruby. Строковая интерполяция является предпочтительным способом:
a_string = "Output was: #{results}"
И это просто работает & trade;, потому что интерполяция строки фактически вызывает to_s
в результате интерполированного выражения.
Кажется, это противоречит духу утки. Пользователь объекта (т. Е. Метод, который получает его в качестве параметра) не должен заботиться о том, что этот объект является , он должен заботиться только о том, что он может сделать . (В этом случае «do» означает «может быть представлен в виде строки / int».)
Я бы сказал, что "можно представить как строку / int" на самом деле не поведение. IOW: «то, что может сделать объект», касается интересного поведения в определенном контексте, а «может быть представлено в виде строки / int» не совсем интересное поведение.
Если вы говорите, что «Status IS-A Integer» (что, по сути, означает to_int
), то из этого следует, например, что вы можете делать арифметику с ним. Но что даже означает для «добавления 42 к файлу, не найденному »? Что такое логарифм успеха? Что такое квадратный корень неудачи?
В приведенном выше примере интерполяции строки интересным является поведение «можно отобразить». И это в основном указывается реализацией #to_s
. Конкатенация двух строк, OTOH, требует двух строк.
Аргументы для "по сути, строка / int" довольно размыты для меня. Например, вы увидите, что Float#to_int
упоминается очень часто. История гласит, что, поскольку число с плавающей точкой всегда имеет целочисленный компонент, to_int
является допустимым методом. Тем не менее, я думаю, что это неверно: число с плавающей точкой является , а не целым числом (так как оно имеет нецелочисленный компонент), и поэтому попытка приравнять их "типичность" не имеет большого смысла. Вы можете законным образом преобразовать число с плавающей точкой в целое число (через усечение), но затем я могу сказать, что могу также преобразовать мой статус в целое число (путем "усечения" всех информация без кода выхода).
Опять же, это довольно слабый аргумент, потому что я на самом деле согласен с вами: это неправильно.
В немецком праве у нас есть принцип, который трудно понять и не вводить в заблуждение, но который, я думаю, применим здесь совершенно. Он называется «Keine Gleichheit im Unrecht» («Нет равенства в неправильности»). Это означает, что основное право Equaliy, которое предоставляется в Конституции, применяется только в пределах Закона. Иными словами: OJ не делает убийство законным.
То есть, если в базовой библиотеке Ruby есть дерьмовый код (и поверьте мне, есть лот ), это еще не значит, что вы тоже можете писать дерьмо: -)
В данном конкретном случае Float#to_int
просто неправильно и не должно существовать. Float
не является подтипом Integer
. На первый взгляд, кажется, что верно обратное, то есть Integer#to_float
является действительным, но на самом деле это тоже не так: в Ruby Integer
имеют произвольную точность, но Float
имеют фиксированную точность. было бы допустимо для реализации Fixnum#to_float
, но это было бы плохой идеей, поскольку Integer
s может магически преобразовывать из Fixnum
в BigInteger
и обратно, и, таким образом, метод #to_float
"волшебным образом" появится и исчезнет.
То, что в итоге помогло me понять разницу между to_x
и to_xyz
, было Array#join
: оно распечатывает элементы массива, разделенные объектом-разделителем. Это делается путем вызова to_s
для каждого элемента массива и to_str
в разделителе. Как только вы поймете , почему он вызывает to_s
для одного и to_str
для другого, вы в основном настроены.
(хотя ваши комментарии к Float#to_int
уже указывают, что вы делаете понимаете.)
Примечание: для двойной диспетчеризации в алгебраическом контексте Ruby фактически использует протокол #coerce
. Итак, если вы хотите, чтобы пример 1 + a_status
работал, вам нужно реализовать Status#coerce
. Но, пожалуйста, не надо.