【问题标题】:Comparison of Classes using the `===` (subsumption operator)使用 `===`(包含运算符)比较类
【发布时间】:2013-12-28 04:54:58
【问题描述】:

TypeOfClass === TypeOfClassfalse 的事实让我觉得违反直觉。在下面的代码中,即使field.class 是同一个类,它也会计算为false

case field.class
when Fixnum, Float
  field + other_field
when Date, DateTime, Time
  field
else
  puts 'WAT?'
end

我这样做了:

Fixnum === Fixnum # => false
Class === Class   # => true

我找到another thread

Integer === 3 # => true
Fixnum === 3  # => true
3.class       # => Fixnum

我找不到这种语言设计的原因。语言设计者在融入这种行为时是怎么想的?

我认为这与another thread 中提供的答案有关。假设Numeric === Integer 并非不自然,因为IntegerNumeric 的更具体类型。但是,它不是:

Numeric === Integer #=> false

我认为case 声明或=== 需要谨慎。如果这个操作符是we think it is,那么Numeric应该是NumericInteger应该是Numeric等等。

有没有人解释一下为什么这个特性没有扩展到类?如果比较类是该类祖先的成员,则返回 true 似乎很容易。

根据下面提交的答案,代码最初是对Time 进行分类(由ActiveSupport:CoreExtensions::Integer::TimeActiveSupport:CoreExtensions::Float::Time 扩展):

Timeframe = Struct.new(:from, :to) do
  def end_date
    case self.to
    when Fixnum, Float
      self.from + self.to
    when Date, DateTime, Time
      self.to
    else  
      raise('InvalidType')
    end
  end
end

在控制台中,我得到:

tf = Timeframe.new(Time.now, 5.months)
# => #<struct Timeframe from=Tue Dec 10 11:34:34 -0500 2013, to=5 months>
tf.end_date
# => RuntimeError: InvalidType
#  from timeframe.rb:89:in `end_date'

【问题讨论】:

  • 再一次,我看不出不将self.to.class 更改为self.to 的原因。
  • 在发布问题之前,我尝试消除了.class 电话,但仍然没有骰子...
  • 好的,为什么它不起作用?尝试在case语句之前打印self.to的类,看看你没有得到任何其他你不期望的类,也许是String
  • 我在发布问题之前尝试过...pry(main)&gt; tf.end_date12960000RuntimeError: InvalidType呃。对不起。

标签: ruby class types switch-statement comparison-operators


【解决方案1】:

我没有真正看到这里的问题。对于类,大小写相等运算符询问左侧参数是否是类(或任何子类)的实例。所以Fixnum === Fixnum 真的问:“Fixnum 类本身是 Fixnum 的子类吗?”不,不是。

Class 本身是一个类吗? Class === Class,是的。

运营商的重点是你不应该去寻找类。开头不使用.class方法的case语句有什么问题?

case field
when Fixnum, Float
  field + other_field
when Date, DateTime, Time
  field
else
  puts 'WAT?'
end

如果您有更复杂的示例,您可以编写自己的 lambda 表达式来简化 case 语句:

field_is_number =  -> x {[Fixnum, Float].include? x.class}
field_is_time   =  -> x {[Date, DateTime, Time].include? x.class}

case field.class
  when field_is_number
    field + other_field
  when field_is_time
    field
  else
    puts 'WAT?'
end

【讨论】:

  • 我承认,我使用的代码示例有点模棱两可。由于ActiveSupport 与时间相关的FloatInteger 扩展,我遇到了这个问题。我希望类型结构能够处理多个变体的初始化参数——1.year 1.year.from_now。实际代码已在上面添加以供参考。我希望澄清。
  • 请在您的原始帖子中添加带有示例或两个示例的真实代码。在奇怪的情况下,创建 lambdas 并在 case-statement 中使用它们可能是一个想法。
  • 在您的示例中,您可以省略.class,代码应该可以正常工作。
  • 我同意 case obj 而不是 case obj.class 应该可以工作,@hirolau,但在这种情况下它不起作用。这只是 Ruby 1.8.7 的事情吗?我不得不承认,我使用较新版本已经有一段时间了,遇到这种情况时不得不重新访问 1.8.7 上的项目。
  • "...大小写相等运算符询问左侧参数是否是类(或任何子类)的实例" 好的,如果是这样,不应该 Numeric === Integer 或 @ 987654336@... 似乎紧随其后,因为Class === Object &amp;&amp; Object === ClassKernel === Object &amp;&amp; Object === Kernel。还是我在继承树上走得太远,进入了不应该被抽象的抽象领域? :) 谢谢,顺便说一句。
【解决方案2】:

这是Module#=== 及其预期行为:

mod === obj → 真或假

大小写相等——如果 anObjectmod 的实例或其中之一,则返回 true mod 的后代。对模块的使用有限,但可以在case 中使用 按类对对象进行分类的语句。

它只是返回obj.kind_of? mod:

Fixnum === Fixnum      #=> false
Fixnum.kind_of? Fixnum #=> false

Class === Class        #=> true
Class.kind_of? Class   #=> true

String === "foo"       #=> true
"foo".kind_of? String  #=> true

3 既是 Integer 又是 Fixnum,因为它的类层次结构:

3.kind_of? Integer     #=> true
3.kind_of? Fixnum      #=> true
3.class.ancestors      #=> [Fixnum, Integer, Numeric, Comparable, Object, Kernel, BasicObject]

Numeric 不是Integer,而是Class

Numeric.kind_of? Integer  #=> false
Numeric.kind_of? Class    #=> true

但是3(2/3)1.23都是Numeric

3.kind_of? Numeric               #=> true
Rational(2, 3).kind_of? Numeric  #=> true
1.23.kind_of? Numeric            #=> true

底线:对于case 语句,只需使用case obj 而不是case obj.class

更新

您收到此错误是因为 5.months 不返回 Integer,而是返回 ActiveSupport::Duration

Integer === 5.months                 #=> false
ActiveSupport::Duration === 5.months #=> true

使用5.months.to_i 调用您的方法或将ActiveSupport::Duration 添加到您的类应该可以解决它。

【讨论】:

  • 我同意case obj 应该可以工作,但事实并非如此。这只是 Ruby 1.8.7 的事情吗?我不得不承认,我使用较新版本已经有一段时间了,不得不重新访问 1.8.7 上的项目。
  • @MarkCoates 请解释“不起作用”,您的意见和预期结果是什么?
  • @MarkCoates 感谢您发布实际输入,我已经更新了我的答案。
  • 您对语义的推断使灯泡闪烁。感谢您的回答。现在它肯定更有意义了,甚至在 Ruby 类型系统的上层抽象中也是如此。干杯! +1
  • ActiveSupport::Duration 很棘手。它使用method_missing 将所有消息委托给底层值。这就是5.months.class 返回Fixnum 的原因。
【解决方案3】:

如果其中一个操作数是一个类,它正在检查第二个操作数是否是这个类的实例。如果它们都是类,它将返回 false,除非其中至少有一个是 Class。

请注意,Class 既是类又是其自身的实例 - 它可能是最大的 ruby​​ 怪异 TBH,但它非常有意义。

我会投票支持保持这种逻辑的原因是我们可以编写那些不错的案例语句而不用将.class 添加到对象中。

总结:

ClassName === object    <=>    object.kind_of? ClassName

但是,如果你真的想覆盖这个用法:

class Class
  def ===(object)
    return object == self if object.is_a? Class
    super
  end
end

【讨论】:

  • 谢谢,@BroiSatse。我也想过这一点,但我认为这如此令人困惑的原因可能是因为一些猴子修补程序,所以我想避免将修补程序提高一个档次。 ;-)
  • 我应该补充一点,如果kind_of?Class === instance 一起工作,那么从逻辑上讲,类级别的kind_of? 应该通过ancestors 检查类型包含。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-28
  • 2023-03-26
  • 2021-11-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多