【问题标题】:Why does Ruby have TrueClass and FalseClass instead of a single Boolean class?为什么 Ruby 有 TrueClass 和 FalseClass 而不是一个布尔类?
【发布时间】:2010-07-07 08:15:05
【问题描述】:

当我发现这个时,我正在序列化值。 Ruby 有一个TrueClass 类和一个FalseClass 类,但它没有Boolean 类。我想知道这是为什么。

我看到了使用Boolean 的一些优势;例如,字符串解析可以集中在上面。

Ruby 开发人员比我聪明,所以肯定有很多我看不到的充分理由。但现在在我看来,OneClassTwoClass 而不是 Fixnum

【问题讨论】:

  • 其他语言(不喜欢打鸭子)是否使用Boolean
  • 从我的脑海中浮现:C++、Java、C#、Python 和 Javascript。另一方面,C 和 Perl 不使用布尔值。
  • C99 确实有一个布尔类型 bool,但您必须包含 stdbool.h 才能访问它(或使用 _Bool)。

标签: ruby language-design


【解决方案1】:

类的目的是将相似的对象或具有相似行为的对象组合在一起。 12 非常相似,因此将它们放在同一个类中非常有意义。 truefalse 然而相似。事实上,它们的全部意义是它们恰好是相反的,并且具有相反的行为。因此,它们不属于同一类。

您能否举例说明您将在Boolean 类中实现哪种常见行为?我什么都想不出来。

让我们看看TrueClassFalseClass 的行为:那里正好有四个 方法。不再。在每种情况下,这两种方法的作用完全相反。您如何以及为什么将其放在一个班级中?

以下是实现所有这些方法的方法:

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 中,有很多“魔法”在幕后进行,实际上并没有由 TrueClassFalseClass 处理,而是硬连线到解释器中。 if&&||! 之类的东西。然而,在 Smalltalk 中,Ruby 借鉴了很多东西,包括 FalseClassTrueClass 的概念,所有这些都被实现为方法,你可以在 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

几年前,我写以上内容只是为了好玩,even published it。除了语法看起来不同(因为 Ruby 使用特殊运算符而我只使用方法)之外,它的行为与 Ruby 的内置运算符完全一样。事实上,我实际上服用了the RubySpec conformance testsuiteported it over to my syntax,它通过了。

【讨论】:

  • 谢谢您,您的评论非常广泛。我已经给出了一个常见行为的例子——字符串解析,如 Boolean.parse("true") => true。你有一点,他们有 4 种方法,他们做相反的事情。但是我不能承认它们不相似;毕竟,true.methods == false.methods。从方法上讲,走路和听起来都像鸭子。
  • 类的目的是将相似的对象组合在一起(除其他外)。我同意 --- 这两个类具有完全相同的四种方法这一事实向我表明它们可能具有相同的类型。他们不共享任何实现是方法继承问题,而不是打字问题。
  • @alexloh:我从来没有说过他们没有相同的类型。我同意你的看法:他们确实具有相同的类型。但这与他们的类没有任何关系:Ruby 是一种面向对象的语言。在面向对象的语言中,对象的类型由其协议定义,而不是由其类定义,这与 Java、C# 或 C++ 等面向 ADT 的语言不同,其中类 类型,但这不适用于 Ruby,因为它不是面向 ADT 的 OO。在面向对象的语言中,类的目的是共享实现,但truefalse共享任何实现。
  • @kikito:将eval 用作!!eval("true") 之类的东西会更好吗?我个人更喜欢为所有内容使用 .to_bool,甚至是 bool(),但它很好。
  • @JörgWMittag 恕我直言,我没有看到两个不共享实现的类。我看到一个明显错失的抽象机会。我也看到了共同祖先的合法用例,还有很多其他用例。我仍然不相信。
【解决方案2】:

2004 年,Matz 本人似乎在 mailing list message 上回答了这个问题。

他的回答的简短版本:“现在它工作正常,添加布尔值没有任何优势”。

我个人不同意这一点;前面提到的“字符串解析”就是一个例子。另一种情况是,当您根据变量的类型对变量应用不同的处理时,(即 yml 解析器)具有“布尔”类很方便 - 它删除了一个“if”。它看起来也更正确,但这是个人意见。

【讨论】:

  • 我认为它遵循了 ruby​​ 哲学,如果你错过了一个布尔类并且 ruby​​ 不提供它,只需自己定义它!
  • 我不知道我该怎么做。我知道课程是开放的,但是......我不确定他们的层次结构是什么。正确知道 TrueClass 和 FalseClass 直接依赖于 Object。我必须在两者之间插入我的布尔类。不要认为这是可能的,但如果你证明我错了会很高兴。
  • 在我看来,让 TrueClass 和 FalseClass 都成为 Boolean 的子类会给你两全其美:不同的类因其不同的行为,但一个公共的基类可以添加常见的行为/执行。它还可以让你做类似 true.is_a 的事情?布尔值,感觉就像他们目前缺少的一样。但就像@kikito 所说,这必须在语言层面上进行更改;我们不能在运行时改变一个类的超类。
  • 同时我也看到了Matz的观点:true只是真理的代表值。 "string" 和 5 也是如此。 “在 Ruby 中,一切都表现为布尔值” [ruby-forum.com/topic/4412411]
  • @kikito:我想这可以用模块来完成。见ruby-forum.com/topic/60154#56346:模块布尔值; end [true, false].each 做 |obj| obj.extend(Boolean) 结束
【解决方案3】:

Ruby forum (2013) 上引用 Matz:

...没有什么真假共同分享,因此没有布尔类。 除此之外,在 Ruby 中,一切都表现为布尔值......

【讨论】:

    【解决方案4】:

    true 和 false 可以由拥有多个值的布尔类管理,但是类对象必须具有内部值,因此必须在每次使用时取消引用。

    相反,Ruby 将 true 和 false 视为长值(0 和 1),每个值对应于一种对象类(FalseClass 和 TrueClass)。通过使用两个类而不是单个布尔类,每个类不需要任何值,因此可以简单地通过其类标识符(0 或 1)来区分。我相信这会转化为 Ruby 引擎内部的显着速度优势,因为在内部 Ruby 可以将 TrueClass 和 FalseClass 视为需要从其 ID 值进行零转换的长值,而 Boolean 对象必须在对其进行评估之前取消引用.

    【讨论】:

    • 这对我来说没有意义。您是说如果我们有一个具有两个值的布尔类,而不是 TrueClass 和 FalseClass 各有一个值,那么 true 和 false 就不再是立即值。为什么会这样?毕竟有不止一个 Fixnum 的实例(明显更多)并且 Fixnums 仍然是立即值。也是次要的挑剔:true 和 false 在内部不表示为 0 和 1,它们表示为 2 和 0(至少在 MRI 中)。
    • 关于 2 和 0 的好点 - 我确定 FalseClass 但不是 TrueClass...无论如何,我的观点是使用 TrueClass 和 FalseClass 允许通过类简单地评估值标识符,而将对象标记为布尔类的标识符则必须在实例值已知之前进行评估。我认为这是通过将所有 ID(通常是指针地址)视为 long 来完成的优化,以便 FixNums 可以是 1 位偏移并直接操作,而不必在评估之前取消引用。
    • @Asher:该值不是“仅通过类标识符评估”。 Ruby 不知道一个值是否为假,因为它的类是 FalseClass。它知道它是假的,因为值为 0。同样它知道一个值为真,因为它是 2。它甚至不将类信息存储在任何地方(实际上没有任何与真或假关联的对象结构,所以会有无处存放课程)。换句话说,用布尔值替换 TrueClass 和 FalseClass 不会产生任何性能差异。
    • 但这正是我的观点。 Ruby 不需要实例化 TrueClass 或 FalseClass,因为 0 和 2 已经对应于它们的值,这也是它们的类。如果有一个布尔类,则需要实例和实例评估(通过内部类解析)。照原样,只有 long 值(0 或 2)需要作为类 ID 进行评估,此时实例评估可以停止。 0 和 2 是直接的并且已经是类信息,这就是为什么不需要在任何地方存储其他任何东西的原因。
    • 我看不出布尔类需要实例评估。使 Boolean 成为一个只有两个可能值的类,存储为常量:true 和 false。 true 由值 2 表示,false 由 0 表示。 Boolean.new 仅返回这两个常量之一。这里不需要实例评估。
    【解决方案5】:

    由于在 Ruby 中除了 falsenil 之外的所有内容都默认为 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)。

    【讨论】:

    • 我认为 ruby​​ 的一个更大的想法是让频繁的任务变得简单。以 String 为例——它的大部分方法都可以用正则表达式、gsub 和 monkeypatching 来实现,但是它们已经“嵌入”到类中,这很方便。布尔值不被认为值得同样处理,这有点奇怪。不过,您的帖子可能对其他人有用,所以 +1。
    • 这不是编程背后的大创意吗? ;-D
    • @SlippD.Thompson 我的意思是当我写它时用“双引号”括起来的字符串值。但是谢谢;重述和澄清:是否有任何关于什么等于虚假的协议?也许还有字符串“nil”和字符串“null”以及符号 :null 和其他一些东西?傻,我知道。任何预制的布尔解析器都会强制用户发表意见,并试图使用内置解析器并得到错误,而不是查看域并负责解析。
    • @SimonB。如果您正在寻找某种关于真实字符串的现有标准,那么您的选择是有限的。使用 == 时,JavaScript 仅将 "1" 等同于 true;所有其他字符串都是假的。 PHP 几乎相反——"0""" == false;所有其他字符串为真。 Perl 和 Python (AFAIK) 像 Ruby 一样工作——所有字符串都是真的。在 Java 中,Boolean.parseBoolean() 将为您提供 "true" 的 true 和 false 的所有其他字符串。 C# 的 Boolean.Parse() 会给你(在空格剥离和小写之后)"true" 为 true,"false" 为 false,并抛出其他所有内容。
    • @SimonB。因此,您的一致性选择是有限的,主要是设计上的。我建议要么让你的扩展高度和明显的类或应用程序特定,或者给它一个时髦的方法名称(例如trueish?),这清楚地表明使用这种方法所做的假设是松散的和临时的,而不是基于任何标准。
    【解决方案6】:

    主要原因仅仅是这样一个事实,即实现布尔表达式比使用意味着转换的布尔类要快得多且简单得多。

    正如 Mongus Pong 告诉你的,当你写“if”时,你要求解释器评估 thing 然后分支。如果您有 Boolean 类,则必须将 thing 的求值转换为 Boolean before 分支(多一步)。

    请记住,这种 ->Boolean 转换可以作为 Boolean 类中的 Ruby 方法使用。然后可以像任何其他 Ruby 方法一样动态更改此方法,从而允许开发人员完全搞砸事情(这确实不是那么严重),但显然,这不允许解释器按应有的方式优化测试。

    你是否意识到它会用一个完整的方法调用来代替一些 CPU 指令操作,这在 Ruby 中代价高昂(记住“发送”方法处理)...

    【讨论】:

    • 对不起,我不遵循效率推理。我想你不必在方法上调用“self”并返回一个值这一事实有一些非常小的收获,但是当整个类都用 C 实现时,这并不相关,是吗?
    • 为什么布尔类会暗示转换,而 TrueClass 和 FalseClass 不会? “如果你有布尔类,你必须在分支之前将 thing 的评估转换为布尔值”这句话背后的逻辑是什么?
    【解决方案7】:

    在 Ruby 中,nil 和 false 都是 false,其他一切都是 true。因此不需要特定的布尔类。

    你可以试试:

    if 5
      puts "5 is true"
    end
    

    5 为真

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

    将打印“nil is false”

    【讨论】:

    • @Mongus Pong:你忘了false 也是假的 :)
    • 真,无,假为假。其他一切都是真的。
    • 你如何从只有 nil 和 false 是“假”(“假”)的事实中推断出不需要布尔类?我看不到这里的逻辑(双关语:))
    • @egarcia 因为这意味着任何类型都可以用来表示布尔值。这与 ruby​​s 动态类型相结合,涵盖了所有必要的场景。
    • @Andrew,该死的英语!我的意思是说“中微子,你说的是真的,nil 和 false 是假的。
    【解决方案8】:

    正如其他人所说,您可以“修补”Ruby。创建自己的班级。这是我想出的东西。 Boolean 类的方法有点傻,但在某些时候它们可能会以编程方式有用。

    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
    

    【讨论】:

    • 这不能回答问题
    猜你喜欢
    • 2023-03-27
    • 1970-01-01
    • 2014-06-30
    • 2011-01-29
    • 1970-01-01
    • 1970-01-01
    • 2016-08-15
    • 2022-11-08
    • 1970-01-01
    相关资源
    最近更新 更多