为什么Ruby具有TrueClass和FalseClass而不是单个布尔类?


73

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

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

Ruby开发人员比我聪明,因此肯定有很多我看不见的充分理由。但是现在在我看来就像是用OneClass和一个TwoClass代替Fixnum


是否使用其他语言(不喜欢鸭式打字)Boolean
安德鲁·格林

1
从我的头开始:C ++,Java,C#,Python和Javascript。另一方面,C和Perl不使用布尔值。
kikito

5
C99确实具有布尔类型,bool但是您必须包括stdbool.h访问它(或使用_Bool)。
亩太短了,

Answers:


61

类的目的是将相似的对象或行为相似的对象组合在一起。1并且2非常相似,因此让他们处于同一个班级是很有意义的。truefalse不过是相似的。实际上,它们的全部目的在于它们彼此完全相反并且具有相反的行为。因此,它们不属于同一类。

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

让我们在行为只是外观,TrueClassFalseClass具有:有完全4分的方法存在。不再。在每种情况下,这两种方法的作用恰恰相反。您如何以及为什么将其放在一个班级?

这是实现所有这些方法的方式:

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,从中红宝石借了很多,包括概念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

几年前,我只是为了好玩而写了上面的文章,甚至出版了。除了语法看起来不同,因为Ruby在我仅使用方法时使用特殊运算符,但它的行为与Ruby的内置运算符完全一样。实际上,我实际上接受了RubySpec一致性测试套件,并将其移植到我的语法中并通过了。


9
谢谢,您的评论非常广泛。我已经给出了一个常见行为的示例-字符串解析,如Boolean.parse(“ true”)=> true。您要指出的是,它们有4种方法,而它们却相反。但是我不能承认它们并不相似。毕竟true.methods == false.methods。在方法上,走路和听起来都像鸭子一样。
kikito 2010年

14
类的目的是将相似的对象组合在一起(以及其他功能)。我同意---并且这两个类具有与四个方法完全相同的事实,对我来说,它们可能具有相同的类型。他们不共享任何实现是方法继承问题,而不是类型问题。
alexloh 2011年

5
@alexloh:我从没说过他们没有相同的类型。我同意你的看法:他们这样做有相同的类型。但这与它们的类没有任何关系:Ruby是一种面向对象的语言。在面向对象的语言中,对象的类型是由协议定义的,而不是由其类定义的,这与Java,C#或C ++等面向ADT的语言不同,其中类类型,但事实并非如此。不适用于Ruby,因为它不是面向ADT的面向对象。在面向对象的语言,一类的目的是实现共享,但truefalse 没有分享任何实现。
约尔格W¯¯米塔格

@kikito:eval像这样更好!!eval("true")吗?我个人更愿意为所有内容甚至使用bool()拥有.to_bool,但是很好。
alanjds 2012年

@AlanJustinoDaSilva:我不知道评估部分。在ruby中,所有事物都有一个to_bool-只是所有事物都返回true,除了nil和false :)
kikito 2012年

23

Matz自己似乎在2004年的邮件列表中回答了这个问题。

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

我个人不同意。前述的“字符串解析”就是一个例子。另一个问题是,当您根据变量的类型对变量进行不同的处理时,具有“ Boolean”类的变量(即yml解析器)很方便-它将删除一个“ if”。它看起来也更正确,但这是个人观点。


我认为它遵循ruby的哲学,即如果您错过了Boolean类,而ruby不提供它,则可以自己定义它!
jp

5
我不知道该怎么办。我知道这些类是开放的,但是...我不确定它们的层次结构是什么。知道TrueClass和FalseClass直接依赖于Object。我必须在两者之间插入我的布尔类。别认为这是可能的,但是如果您证明我做错了,那将很高兴。
kikito 2010年

2
在我看来,让TrueClass和FalseClass都是Boolean的子类将为您提供两全其美的方法:不同的类具有独特的行为,但是有一个通用的基类,可以在其中添加通用的行为/实现。它还会让您执行true.is_a之类的事情?布尔值,感觉好像它们当前正在丢失。但是就像@kikito所说的那样,必须在语言级别进行更改;我们不能在运行时更改类的超类。
泰勒里克(Tyler Rick)

同时,我可以看到Matz的观点:true只是真理的代表价值。“字符串”和5也是如此。“在Ruby中,所有内容均表现为布尔值” [ ruby-forum.com/topic/4412411]
泰勒·里克

3
@kikito:我想可以用模块来完成。参见ruby-forum.com/topic/60154#56346:布尔模块;end [true,false]。每个| obj | obj.extend(Boolean)end
泰勒·里克

5

Ruby论坛上引用Matz (2013)

...没有什么共同点是假,因此没有布尔类。除此之外,在Ruby中,所有内容都表现为布尔值。


4

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

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


2
这对我来说没有意义。您说的是,如果我们有一个具有两个值的布尔类,而不是分别具有一个值的TrueClass和FalseClass,则true和false不再是立即值。为什么会这样呢?毕竟有一个以上的Fixnum实例(明显更多),并且Fixnums仍然是立即值。较小的nitpick:true和false在内部不表示为0和1,它们分别表示为2和0(至少在MRI中)。
sepp2k 2010年

关于2和0的要点-我确定是FalseClass而不是TrueClass ...无论如何,我的观点是使用TrueClass和FalseClass允许简单地通过类标识符来评估值,而将标记为然后必须在知道实例值之前对对象作为布尔类进行评估。我认为这与通过将所有ID(通常是指针地址)视为long进行的优化相同,这样FixNums可以偏移1位并直接对其进行操作,而不必在评估之前取消引用。
Asher 2010年

2
@Asher:该值不是“仅通过类标识符求值”。Ruby不知道值是假的,因为它的类是FalseClass。它知道它是错误的,因为值是0。同样地,它知道值是正确的,因为它是2。它甚至不将类信息存储在任何地方(实际上没有任何对象结构与true或false相关联,因此会有无处可存储该类)。换句话说,用Boolean替换TrueClass和FalseClass不会产生任何性能差异。
sepp2k 2010年

但这正是我的意思。Ruby不需要实例化TrueClass或FalseClass,因为0和2已经对应于它们的值,这也是它们的类。如果存在布尔类,则将需要实例和实例评估(通过内部类解析)。照原样,仅需要将长值(0​​或2)作为类ID进行评估,此时实例评估可以停止。0和2直接且已经是类信息,这就是为什么其他任何地方都不必存储任何内容的原因。
Asher

我看不到布尔类如何需要实例评估。将Boolean设为只有两个可能值存储为常量的类:true和false。true由值2表示,false由0表示。Boolean.new仅返回这两个常量之一。此处无需实例评估。
Ragmaanir'7

3

由于在Ruby中,除了false和以外的所有内容nil默认情况下都为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)。


2
我认为关于红宝石的一个更大的想法是使频繁的任务变得简单。以String为例-它的大多数方法都可以通过正则表达式,gsub和Monkeypatching来实现,但是它们已经被“烘焙”到该类中了,这很方便。布尔值不被认为值得同等对待有点奇怪。不过,您的信息可能会对其他人有用,因此+1。
kikito 2011年

那不是编程背后的大创意吗?;-D
SLIPP D.汤普森

1
@ SlippD.Thompson我的意思是将字符串值括在“双引号”中时的字符串值。但是谢谢 驳斥并澄清:对于什么等于假,是否有任何共识?也许还有字符串“ nil”和字符串“ null”以及符号:null和其他一些东西?傻,我知道。任何预制的布尔解析器都会迫使用户发表意见,并试图使用内置解析器并获取错误,而不是查看域并负责解析。
西蒙

1
@SimonB。如果您正在寻找某种关于真实字符串的现有标准,那么您的选择将受到限制。JavaScript仅"1"在使用时等于true ==;其他所有字符串均为假。PHP几乎是相反的,"0"而且是"" ==错误的。所有其他字符串为true。Perl和Python(AFAIK)的工作方式类似于Ruby,所有字符串都是正确的。在Java中,每隔一个字符串Boolean.parseBoolean()都会为true"true"和false。C#Boolean.Parse()将为您(在去除空格和缩进空白之后)为true,为"true"false"false"并抛出其他所有内容。
Slipp D. Thompson

1
@SimonB。因此,主要是通过设计来限制您的一致性选择。我建议您要么高度扩展您的扩展,显然是特定于类或应用程序的扩展,要么给它一个时髦的方法名称(例如trueish?),以明确表明使用此方法所做的假设是宽松且临时的,不基于任何标准。
Slipp D. Thompson

1

在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”


3
@Mongus Pong:您也忘记了这false是错误的:)
alex.zherdev

true,nil和false是false。其他一切都是真的。
Mongus Pong

3
您如何从仅nil和false为“ false”(“ falsy”)的事实推论出不需要布尔类?我看不到这里的逻辑(双关语:))
kikito 2010年

@egarcia,因为这意味着可以使用任何类型来表示布尔值。这与rubys动态类型结合涵盖了所有必要的方案。
孟格斯·庞

1
@Andrew,该死的英语!我的意思是说“中微子,您所说的是真的,零和假是假
。–庞

1

主要原因仅仅是因为这样的事实,即与布尔类(暗示转换)相比,它现在比现在更容易实现布尔表达式。

正如孟格斯·庞(Mongus Pong)告诉您的那样,当您输入“ if”时,您要求解释器对事物进行评估,然后分支。如果您具有布尔类,则必须分支之前事物的评估转换为布尔值(再执行一步)。

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

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


抱歉,我不遵循效率推理。我想象一下,您不必在方法上调用“ self”并返回一个值,这会带来一些非常小的好处,但是当整个类都用C实现时,这无关紧要吗?
kikito 2010年

4
为什么布尔类表示转换,但TrueClass和FalseClass不表示转换?“如果您具有布尔类,则必须thing在分支之前将的求值转换为布尔值”该语句背后的逻辑是什么?
sepp2k

-2

正如其他人所说,您可以“修补” 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

1
这不能回答问题
felipe
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.