Reputation: 33
The fact that TypeOfClass === TypeOfClass
is false
strikes me as counter-intuitive. In the following code, even if field.class
is the same class, it evaluates to false
:
case field.class
when Fixnum, Float
field + other_field
when Date, DateTime, Time
field
else
puts 'WAT?'
end
I did this:
Fixnum === Fixnum # => false
Class === Class # => true
I found another thread:
Integer === 3 # => true
Fixnum === 3 # => true
3.class # => Fixnum
I fail to find a reason for this language design. What were the language designers thinking when they baked in this behavior?
I think this is relevant to the answer provided in another thread. It is not unnatural to assume that Numeric === Integer
since an Integer
is a more specific type of Numeric
. But, it isn't:
Numeric === Integer #=> false
I think case
statements or ===
requires caution. If this operator is what we think it is , then, a Numeric
should be a Numeric
, an Integer
should be a Numeric
, etc.
Does anyone have an explanation of why this feature doesn't extend to classes? It seems like it would be easy enough to return true
if the compared class is a member of the class' ancestors.
Based on an answer submitted below, the code was originally classifying Time
(as extended by ActiveSupport:CoreExtensions::Integer::Time
and ActiveSupport: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
and in the console, I get:
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'
Upvotes: 1
Views: 350
Reputation: 13911
I do not really see the problem here. For classes, the case equality operator asks whether the left hand argument is an instance of the class (or any subclass). So Fixnum === Fixnum
really asks: "Is the Fixnum class itself a subclass of Fixnum?" No it is not.
Is the class Class
itself a class? Class === Class
, yes it is.
The point of the operator is that you should not need to go look for the class. What is wrong with using the case statement without the .class
method in the beginning?
case field
when Fixnum, Float
field + other_field
when Date, DateTime, Time
field
else
puts 'WAT?'
end
If you have a more complex example you can write your own lambdas to make the case statement easier:
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
Upvotes: 5
Reputation: 114188
That's Module#===
and its intended behavior:
mod === obj → true or false
Case Equality—Returns
true
if anObject is an instance of mod or one of mod’s descendants. Of limited use for modules, but can be used incase
statements to classify objects by class.
It simply returns 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
is both, an Integer
and a Fixnum
because of its class hierarchy:
3.kind_of? Integer #=> true
3.kind_of? Fixnum #=> true
3.class.ancestors #=> [Fixnum, Integer, Numeric, Comparable, Object, Kernel, BasicObject]
Numeric
is not an Integer
, it's a Class
:
Numeric.kind_of? Integer #=> false
Numeric.kind_of? Class #=> true
But 3
, (2/3)
and 1.23
are all Numeric
:
3.kind_of? Numeric #=> true
Rational(2, 3).kind_of? Numeric #=> true
1.23.kind_of? Numeric #=> true
Bottom line: for case
statements, just use case obj
instead of case obj.class
.
Update
You are getting this error because 5.months
doesn't return an Integer
, but a ActiveSupport::Duration
:
Integer === 5.months #=> false
ActiveSupport::Duration === 5.months #=> true
Calling your method with 5.months.to_i
or adding ActiveSupport::Duration
to your classes should fix it.
Upvotes: 1
Reputation: 44695
If one of the operands is a class, it is checking whether the second one is this class instance. If they are both classes it will return false unless at least one of them is Class.
Note that Class is both a class and an instance of itself - it's probably biggest ruby weird TBH, but it makes perfect sense.
The reason I would vote for keeping this logic is that we can write those nice case statements without adding .class
to objects.
Summary:
ClassName === object <=> object.kind_of? ClassName
However, if you really want to override this use:
class Class
def ===(object)
return object == self if object.is_a? Class
super
end
end
Upvotes: 1