Последствия реализации to_int и to_str в Ruby - PullRequest
5 голосов
/ 12 ноября 2009

I имеет класс , который предоставляет строковое значение и значение типа int (выходные данные команды и код выхода соответственно). В дополнение к экспонированию через to_s и to_i я также использую to_str и to_int, например:

class Status
  def to_s
    @output
  end
  alias :to_str :to_s

  def to_i
    @status.exitstatus
  end
  alias :to_int :to_i
end

Моя идея заключается в том, чтобы иметь возможность использовать этот объект в максимально возможном количестве ситуаций. его принудительное использование в виде строки или целого числа повышает это удобство использования. Например, я могу объединить объект со строкой:

a_string = "Output was: " + results

(Я хотел использовать это в качестве примера int принуждения, но Fixnum. + Это не нравится, поэтому он на самом деле не работает:)

an_int = 1 + results

Все, что я до сих пор читал, говорит о том, что это, вероятно, "плохой" поступок. Общая тема звучит так: «Используйте to_s / to_i, когда ваш объект может быть представлен в виде строки / int, но to_str / to_int только в том случае, если ваш объект в основном строка /int".

Нет сомнений в том, что мой класс не является "принципиально" строкой или целым числом. тем не мение У меня есть некоторые проблемы с этим правилом:

  1. Это делает мой класс менее гибким / пригодным для использования. Например: я не мог использовать String. + Для объединения вывода Status с другой строкой, если у меня не было Status.to_str.
  2. Кажется, это противоречит духу утки. Пользователь объекта (т. Е. Метод, который получает его в качестве параметра) не должен заботиться о том, чем является объект , он должен заботиться только о том, что он может делать . (В этом случае «do» означает «может быть представлен в виде строки / int».)
  3. Аргументы для "по сути является строкой / int" довольно размыты для меня. Например, вы увидите, что Float.to_int упоминается очень часто. История гласит, что, поскольку число с плавающей точкой всегда имеет целочисленный компонент, to_int является допустимым методом. Тем не менее, я думаю, что это неверно: число с плавающей точкой является , а не целым числом (так как оно имеет нецелочисленный компонент), и поэтому попытка приравнять их "типичность" не имеет большого смысла. Вы можете законно преобразовать число с плавающей точкой в ​​целое число (через усечение), но затем я могу сказать, что я также могу преобразовать мой статус в целое число (путем "усечения" всех информация без кода выхода).

Итак, мой вопрос: есть ли реальный (т.е. практический) вред при реализации to_str и to_int?


Обновление: Йорг Миттаг привел пример, который заставил меня задуматься о чем-то. Перефразируя вопрос: действительно ли нужно иметь to_str / to_int, когда у вас уже есть to_s / to_i? (Помимо того факта, что определенные методы уже ожидают to_str сверх to_s)

Например, в примере Array.join Jörg члены массива конвертируются через to_s, а разделитель - через to_str. Но действительно ли это необходимо? Если вместо этого Array.join вызывается разделитель.to_s, вы можете успешно передать ему гораздо больше объектов (например, целые числа, символы и т. Д.) И получить гораздо большую гибкость. Выгодно ли Ruby иметь это разделение?

Ответы [ 2 ]

8 голосов
/ 12 ноября 2009

Это делает мой класс менее гибким / пригодным для использования. Например: я не мог использовать 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. Но, пожалуйста, не надо.

4 голосов
/ 12 ноября 2009

Дух утилитарной печати определенно не заставит кого-то искать исходный код объекта Status, чтобы выяснить, что возвращается.

Лично я думаю, что вы должны раскрыть текстовые результаты и состояние выхода с помощью двух методов экземпляра:

class Status

  ...

  def message
    @output
  end

  def exit_status
    @status.exitstatus
  end

end

А затем используйте его следующим образом

a_string = "Output was: " + results.message
a_int    = 1 + results.exit_status

Что сразу имеет смысл для любого, кто читает код, ИМХО.

...