4

If I have klass that may (or may not) be an instance of Class, how can I check klass against given classes in case-statement style to see which class it is a subclass of?

For example, suppose klass happens to be Fixnum. When I check klass against Integer, Float, ..., I want it to match Integer. This code:

case klass
when Integer ...
when Float ...
else ...
end

will not work because it will check whether klass is an instance of the classes. I want to check whether klass is a subclass of the classes (or is itself that class).

This is the best I can do so far, but I feel it may be an overkill and is not efficient:

class ClassMatcher
  def initialize klass; @klass = klass end
  def === other; other.kind_of?(Class) and other <= @klass end
end

class Class
  def matcher; ClassMatcher.new(self) end
end

klass = Fixnum
case klass
when Integer.matcher then puts "It is a subclass of Integer."
when Float.matcher then puts "It is a subclass of Float."
end
# => It is a subclass of Integer.
sawa
  • 165,429
  • 45
  • 277
  • 381
  • Why not just check if `obj.ancestors.include? Integer`, etc? – Chris Heald Dec 28 '13 at 05:41
  • @ChrisHeald Why not replace all (conventional) case statements with `obj.kind_of?(foo)`, etc.? – sawa Dec 28 '13 at 05:43
  • I'm not sure I understand the question. Ruby's case statement uses ===, and the behavior of === in regards to class comparisons is pretty well documented. If it won't do what you want in this case, then use something that will. – Chris Heald Dec 28 '13 at 05:47
  • @ChrisHeald I don't think you understand the question at all. – sawa Dec 28 '13 at 05:49
  • Perhaps you've failed to explain it properly, then. As I read it, you're expecting invalid behavior from `case`. – Chris Heald Dec 28 '13 at 05:51
  • @ChrisHeald I never wrote I want to use `case` statement itself. That is your misinterpretation. I wrote I want to do it in case-statement style. – sawa Dec 28 '13 at 05:53
  • Would checking `klass.class.ancestors.include?` for each of the specific cases (`Integer`, `Float`,...) be what you're looking for? – pjs Dec 28 '13 at 05:59
  • @pjs No. It would not. – sawa Dec 28 '13 at 06:06
  • @sawa what is wrong with pjs's ancesor? – sunnyrjuneja Dec 28 '13 at 06:21
  • When `klass` is `Fixnum`, `klass.class` is `Class`. It will not match against `Integer` because `Class`'s ancestors do not include `Integer`. – sawa Dec 28 '13 at 06:40

4 Answers4

7

Something more functional?

is_descendant = lambda { |sample, main| main <= sample }
not_a_class = lambda { |x| !x.kind_of?(Class) }

mine = Fixnum

case mine
when not_a_class then raise 'Not a class' # Credits to @brymck
when is_descendant.curry[Float] then puts 'Float'
when is_descendant.curry[Integer] then puts 'Integer'
else raise 'Shit happens!'
end

# ⇒ Integer
Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
  • This is the second answer after davogones' that is close to answering what I asked. This will work, except that it will raise an error in case `mine` happens not to be a class. – sawa Dec 28 '13 at 06:27
  • 2
    @sawa Aside from filtering out non-classes before the case statement, you could add another lambda like `not_a_class = lambda { |x| !x.kind_of?(Class) }` and put `when not_a_class ...` at the top of the case statement. – brymck Dec 28 '13 at 06:43
  • @brymck That is a good idea. – sawa Dec 28 '13 at 06:49
  • Lambdas, nice! Forgot you could use those. – davogones Dec 28 '13 at 15:16
3
case 
when klass <= Integer ...
when klass <= Float ...
else ...
end

Repetitive, but probably the only way to do what you're looking for.

davogones
  • 7,321
  • 31
  • 36
  • This is the only answer so far that is close to answering what I want, and it is actually close to what I have so far. But note that I wrote `klass` may not be an instance of `Class`. Your code raises an error in such case. – sawa Dec 28 '13 at 06:02
  • That's not a use case of case statements then. – Bart Dec 28 '13 at 06:13
2

How about using Enumerable#find and Module#<= ?

obj = Fixnum
[Float, Integer].find { |cls| obj.kind_of?(Class) && obj <= cls }
# => Integer

or simple if, elsif, ..:

obj = Fixnum
if ! obj.kind_of? Class; puts 'It is not a class'
elsif obj <= Integer; puts 'It is a subclass of Integer'
elsif obj <= Float; puts 'It is a subclass of Float'
else; puts 'other'
end
# => It is a subclass of Integer
falsetru
  • 357,413
  • 63
  • 732
  • 636
  • I don't want the matching class to be returned. I want to write a condition based on it. – sawa Dec 28 '13 at 05:44
  • You could use this statement as the case condition. – Chris Heald Dec 28 '13 at 05:45
  • @falsetru How can I do that? – sawa Dec 28 '13 at 05:48
  • @sawa, `case [Float, Integer].find { |cls| Fixnum < cls }.to_s ; when 'Integer' .....` – falsetru Dec 28 '13 at 05:51
  • @falsetru That may work, but it is not clean code. – sawa Dec 28 '13 at 05:54
  • @sawa, Alternatively you can build a hash that maps class to proc. – falsetru Dec 28 '13 at 05:55
  • 1
    @falsetru: But that's messy when you're checking for `<=` rather than just `==` on the classes. – mu is too short Dec 28 '13 at 05:58
  • @muistooshort, What do you mean **that**? If you mean *hash* suggestion, I meant using the hash with `[Float, Integer].find { |cls| Fixnum <= cls }`. – falsetru Dec 28 '13 at 06:06
  • @falsetru You would probably know if you actually try to implement that. – sawa Dec 28 '13 at 06:09
  • @sawa, [I tried to implement it](http://ideone.com/px6pCG). I still don't know. Please explain me. – falsetru Dec 28 '13 at 06:14
  • I thought you were talking about a hash with class keys and lambda values. I'm still not sure what sawa is trying to do here (besides probably trying to be overly clever when something simple and straight forward would be sufficient). – mu is too short Dec 28 '13 at 06:30
  • @muistooshort, Thank you for comment. I agree with you. – falsetru Dec 28 '13 at 06:31
  • @muistooshort Do you have suggestions for something simple and straightforward? – sawa Dec 28 '13 at 06:33
  • @falsetru I was thinking something similar to muistooshort. I got what you mean. But your answer as is in incomplete. – sawa Dec 28 '13 at 06:37
  • @sawa: If you had of explained yourself clearly then I would have ended up with something like mudasobwa's. But you ended up revealing important bits and pieces in various comments all over the place. If you had of started off with a clear and complete question then there wouldn't be any arguing or comment threads to muddy the waters. – mu is too short Dec 28 '13 at 18:22
1

Well, actually it will:

case Class
when Module
  puts 'Class is a subclass of Module'
end

Edit:

Only that comes to my mind after your explanations:

case
when Fixnum < Integer
  puts 'Fixnum < Integer'
end
Bart
  • 2,606
  • 21
  • 32