Как создать объект, который действует как ложь в Ruby

Я хочу сделать это

lol = Klass.new(values)

unless lol 
   print "false"
end

lol.other_method  # it is not nil or false, it is a Klass instance!

Но lol в этом случае не является нильским или ложным, а является объектом, который может действовать как ложный, основанный на некотором внутреннем значении. У меня есть эта альтернатива

lol = Klass.new(values)

unless lol.to_bool
   print "false"
end

Но это уродливое ИМХО.

Я думал о расширении FalseClass или игре с ==, но без успеха. Любые идеи?

Ответ 1

К сожалению, это невозможно.

Это один из тех раздражающих случаев, когда Ruby не является объектно-ориентированным. В OO должно быть возможно, чтобы один объект имитировал другой (фактически, в зависимости от того, о ком вы спрашиваете, это само определение OO - помните, что OO вышел из симуляции), но невозможно построить объект, который имитирует false.

Это связано с тем, что в Ruby структуры условного управления запекаются на языке и не переходят на отправку сообщений, тогда как на других языках OO они являются просто регулярными сообщениями (или, по крайней мере, переводить на сообщения, как, например, for в Ruby переводится в each). Например, в Smalltalk булевы на самом деле реализованы с использованием церковной кодировки логических букв, которые вы знаете из Lambda Calculus, и переведены в Ruby, они выглядят примерно так:

class FalseClass
  def if(&block)
    # do nothing
  end

  def if_then_else(then_lambda, else_lambda)
    else_lambda.()
  end

  def not
    true
  end

  def and(&block)
    self
  end

  def or(&block)
    block.()
  end
end

И TrueClass - это просто зеркальное изображение:

class TrueClass
  def if(&block)
    block.()
  end

  def if_then_else(then_lambda, else_lambda)
    then_lambda.()
  end

  def not
    false
  end

  def and(&block)
    block.()
  end

  def or(&block)
    self
  end
end

И тогда вместо чего-то вроде

if 2 < 3 then foo end
if 2 < 3 then bar else baz end

У вас будет

(2 < 3).if { foo }
(2 < 3).if_then_else(-> { bar }, -> { baz })

# using the new keyword arguments in Ruby 2.0, it almost reads like Smalltalk:
class FalseClass
  def if(then: -> {}, else: -> {})
    else.()
  end
end

class TrueClass
  def if(then: -> {}, else: -> {})
    then.()
  end
end

(2 < 3).if(then: -> { bar }, else: { baz })

Таким образом, вы можете легко создать объект, который имитирует false просто путем реализации соответствующих методов.

В других случаях, когда какой-то объект действительно абсолютно должен быть экземпляром определенного класса, а не просто говорить о правильном протоколе, Ruby предоставляет escape-люк. Например, если для метода действительно требуется Array в качестве аргумента, он сначала попытается вызвать to_ary, чтобы хотя бы дать вам возможность конвертировать ваш объект в Array. То же самое относится к to_str, to_int, to_proc, to_float и т.д. Но нет эквивалентного протокола to_bool.

Ответ 2

Короткий ответ: Не делайте этого.

Долгий ответ заключается в том, что в Ruby есть только две вещи, которые оцениваются как false: false и nil. Буквально все остальное оценивается как истинное.

Многие приложения зависят от этого поведения и, вероятно, будут работать неправильно, если вы каким-то образом измените это. Это как определение 1 как четное число. Это вызовет проблемы.