Почему в Ruby вместо одного логического класса есть TrueClass и FalseClass? - PullRequest
71 голосов
/ 07 июля 2010

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

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

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

Ответы [ 8 ]

59 голосов
/ 07 июля 2010

Цель класса - объединить похожие объекты или объекты с похожим поведением. 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 и перенес его на мой синтаксис и он прошел.

19 голосов
/ 12 июля 2010

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

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

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

4 голосов
/ 31 марта 2014

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

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

3 голосов
/ 22 марта 2011

Поскольку все, кроме false и nil в Ruby по умолчанию имеют значение true, вам нужно будет только добавить синтаксический анализ в 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's 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).

3 голосов
/ 07 июля 2010

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

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

1 голос
/ 07 июля 2010

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

Как сказал вам Монгус Понг, когда вы пишете "если", вы просите переводчика оценить вещь , а затем переходить. Если бы у вас был логический класс, вам пришлось бы преобразовать оценку вещь в логический до ветвления (еще один шаг).

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

Понимаете ли вы, что это заменит несколько операций с процессором на полный вызов метода, что дорого обходится в Ruby (вспомните обработку метода "send") ...

1 голос
/ 07 июля 2010

В Ruby ноль и ложь ложны, а все остальное верно. Следовательно, нет необходимости в определенном логическом классе.

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

if 5
  puts "5 is true"
end

5 оценивается как истинное

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

Напечатает "nil is false"

0 голосов
/ 23 марта 2017

Как уже говорили другие, вы могли бы "исправить" 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
...