Почему Ruby имеет TrueClass и FalseClass вместо одного класса Boolean?

Я работал над сериализацией значений, когда узнал об этом. Ruby имеет класс TrueClass и FalseClass, но он не имеет класса Boolean. Я хотел бы знать, почему это.

Я вижу некоторые преимущества при использовании Boolean; например, синтаксический анализ может быть централизован на нем.

Разработчики Ruby умнее меня, поэтому должно быть много веских причин, которых я просто не вижу. Но сейчас мне кажется, что OneClass и TwoClass вместо Fixnum.

Ответ 1

Кажется, что Мац сам ответил на этот вопрос в сообщении списка рассылки в 2004 году.

Краткая версия его ответа: "прямо сейчас он работает нормально, добавление булева не дает никакого преимущества".

Лично я не согласен с этим; вышеупомянутый "синтаксический анализ строк" ​​является одним из примеров. Другое дело в том, что когда вы применяете различное обращение к переменной в зависимости от ее типа, (например, синтаксический анализатор yml), имеющего класс "Boolean", это удобно - он удаляет одно "если". Это также выглядит более правильным, но это личное мнение.

Ответ 2

Цель класса - группировать похожие объекты или объекты с похожим поведением вместе. 1 и 2 очень похожи, поэтому для них совершенно разумно находиться в одном классе. true и false, однако, не похожи. На самом деле, все дело в том, что они совершенно противоположны друг другу и имеют противоположное поведение. Поэтому они не принадлежат к одному классу.

Можете ли вы привести пример того, какое общее поведение вы бы применили в классе Boolean? Я ничего не могу придумать.

Давайте просто посмотрим на поведение, которое TrueClass и FalseClass имеют: там точно четыре метода. Больше не надо. И в каждом отдельном случае эти два метода делают все наоборот. Как и почему вы поместили это в один класс?

Здесь вы реализуете все эти методы:

class TrueClass
  def &(other)
    other
  end

  def |(_)
    self
  end

  def ^(other)
    !other
  end

  def to_s
    'true'
  end
end

И теперь наоборот:

class FalseClass
  def &(_)
    self
  end

  def |(other)
    other
  end

  def ^(other)
    other
  end

  def to_s
    'false'
  end
end

Конечно, в Ruby существует много "магии", которая происходит за кулисами, и на самом деле она не обрабатывается TrueClass и FalseClass, а скорее сложна в интерпретаторе. Такие вещи, как if, &&, || и !. Однако в Smalltalk, из которого Ruby много заимствовал, включая концепцию FalseClass и TrueClass, все они реализованы также как методы, и вы можете сделать то же самое в Ruby:

class TrueClass
  def if
    yield
  end

  def ifelse(then_branch=->{}, _=nil)
    then_branch.()
  end

  def unless
  end

  def unlesselse(_=nil, else_branch=->{})
    ifelse(else_branch, _)
  end

  def and
    yield
  end

  def or
    self
  end

  def not
    false
  end
end

И снова наоборот:

class FalseClass
  def if
  end

  def ifelse(_=nil, else_branch=->{})
    else_branch.()
  end

  def unless
    yield
  end

  def unlesselse(unless_branch=->{}, _=nil)
    ifelse(_, unless_branch)
  end

  def and
    self
  end

  def or
    yield
  end

  def not
    true
  end
end

Несколько лет назад я написал выше только для удовольствия и даже опубликовал его. Помимо того, что синтаксис выглядит иначе, поскольку Ruby использует специальные операторы, в то время как я использую только методы, он ведет себя точно так же, как встроенные операторы Ruby. Фактически, я фактически взял метод проверки соответствия RubySpec и портировал его на мой синтаксис и он проходит.

Ответ 3

Цитирование Matz на Ruby forum (2013):

... Там нет ничего истинного и ложного, обычно используемого, поэтому не существует булевского класса. Кроме того, в Ruby все ведет себя как булево значение....

Ответ 4

true и false может управляться булевым классом, который содержит несколько значений, но тогда объект класса должен иметь внутренние значения и, следовательно, должен быть удален с каждым использованием.

Вместо этого Ruby обрабатывает true и false как длинные значения (0 и 1), каждый из которых соответствует типу класса объекта (FalseClass и TrueClass). Используя два класса вместо одного Boolean-класса, каждый класс не требует каких-либо значений и поэтому может различаться просто его идентификатором класса (0 или 1). Я считаю, что это приводит к значительным преимуществам скорости, присущим внутреннему механизму Ruby, поскольку внутренне Ruby может обрабатывать TrueClass и FalseClass как длинные значения, которые требуют нулевого перевода со своего значения ID, тогда как логический объект должен быть удален, прежде чем его можно будет оценить,

Ответ 5

Так как все, кроме false и nil, по умолчанию оценивают true в Ruby, вам нужно будет только добавить синтаксический анализ в String.

Что-то вроде этого может работать:

class Object

  ## Makes sure any other object that evaluates to false will work as intended,
  ##     and returns just an actual boolean (like it would in any context that expect a boolean value).
  def trueish?; !!self; end

end

class String

  ## Parses certain strings as true; everything else as false.
  def trueish?
    # check if it a literal "true" string
    return true if self.strip.downcase == 'true'

    # check if the string contains a numerical zero
    [:Integer, :Float, :Rational, :Complex].each do |t|
      begin
        converted_number = Kernel.send(t, self)
        return false if converted_number == 0
      rescue ArgumentError
        # raises if the string could not be converted, in which case we'll continue on
      end
    end

    return false
  end

end

При использовании это даст вам:

puts false.trueish?   # => false
puts true.trueish?    # => true
puts 'false'.trueish? # => false
puts 'true'.trueish?  # => true
puts '0'.trueish?     # => false
puts '1'.trueish?     # => true
puts '0.0'.trueish?   # => false
puts '1.0'.trueish?   # => true

Я считаю, что часть "большой идеи", стоящая за Ruby, заключается в том, чтобы просто сделать поведение, которое вы желаете, присущим вашей программе (например, логический синтаксический анализ), а создавая полностью инкапсулированный класс, который живет в нем собственный мир пространства имен (например, BooleanParser).

Ответ 6

В Ruby nil и false ложны, а все остальное - true. Следовательно, нет необходимости в конкретном булевом классе.

Вы можете попробовать:

if 5
  puts "5 is true"
end

5 соответствует true

if nil
    puts "nil is true"
else
    puts "nil is false"
end

Будет напечатан "nil is false"

Ответ 7

Основной причиной является просто тот факт, что это намного быстрее и проще для реализации булевых выражений, чем в настоящее время, чем с булевым классом, который подразумевал бы преобразование.

Как сказал вам Mongus Pong, когда вы пишете "if", вы просите интерпретатора оценить вещь, а затем ответкить. Если у вас есть класс Boolean, вам нужно будет преобразовать оценку вещи в булевую перед ветвлением (еще один шаг).

Помните, что такое -Boolean-преобразование будет доступно как метод Ruby в булевом классе. Затем этот метод можно было бы динамически изменить, как и любой другой метод Ruby, позволяющий разработчику полностью испортить вещи (что не так уж и серьезно), но это явно не позволяет интерпретатору оптимизировать тесты, как они должны.

Вы понимаете, что он заменит несколько операций с инструкциями CPU полным вызовом метода, который является дорогостоящим в Ruby (запомните обработку метода отправки)...

Ответ 8

Как говорили другие, вы можете "запланировать" Ruby. Создайте свой собственный класс. Вот что я придумал. Методы в булевом классе немного глупы, но в какой-то момент они могут быть полезны программно.

class Boolean
  def self.new(bool)
    bool
  end

  def self.true
    true
  end

  def self.false
    false
  end
end

class FalseClass
  def is_a?(other)
    other == Boolean || super
  end

  def self.===(other)
    other == Boolean || super
  end
end

class TrueClass
  def is_a?(other)
    other == Boolean || super
  end

  def self.===(other)
    other == Boolean || super
  end
end