views:

1582

answers:

1

Hi, I have a class that I want to compare to both strings and symbols in a case statement, so I thought that I just override the ===() method for my class and all would be gold. However my ===() method never gets called during the case statement. Any ideas?

Here is some example code, and what happens in a irb session:

class A
   def initialize(x)
      @x=x #note this isn't even required for this example
   end
   def ===(other)
      puts "in ==="
      return true
   end
end

irb(main):010:0> a=A.new("hi")
=> #
irb(main):011:0> case a
irb(main):012:1> when "hi" then 1
irb(main):013:1> else 2
irb(main):014:1> end
=> 2

(it never prints the message and should always return true anyway) Note that ideally I'd like to do a

def ===(other)
          #puts "in ==="
          return @x.===(other)
end

Thanks in advance.

+4  A: 

The expression after the 'case' keyword is the right hand side of the === expression, and the expression after the 'when' keyword is on the left hand side of the expression. So, the method that is being called is String.===, not A.===.

A quick approach to reversing the comparison:

class Revcomp
    def initialize(obj)
        @obj = obj
    end

    def ===(other)
        other === @obj
    end

    def self.rev(obj)
        Revcomp.new(obj)
    end
end

class Test
    def ===(other)
        puts "here"
    end
end

t = Test.new

case t
when Revcomp.rev("abc")
    puts "there"
else
    puts "somewhere"
end
janm
Cool. That explains it, and it works! Does this way of doing case statements seem counter intuitive?
Good. Yes, it does seem a bit messy, but I didn't give the syntax much thought for this answer. You can probably come up with some more concise syntax, or you can do something evil like override operator=== in the classes you want to have on the LHS.
janm
Thanks - I can be a bit more concise but it also makes me think I'm maybe using the wrong tool (case) for the job, but it does work, so thank you ( I should have asked this yesterday ).
No problem. You could also do something simple like have a method on your class that returns the string you want to use in the comparison. That way you avoid doing anything special with === at all.
janm
It makes perfect sense: a.===(b) means "Hey, 'a', is 'b' a member of you?" It doesn't make sense for a Number to know whether it is a member of a Range, but a Range should know whether a Number is inside itself. So, it makes sense for '1..3 === 2' and 'case 2 when 1..3' to mean the same thing.
Jörg W Mittag
BTW: don't take "member" too literally. I always think about it like this: a === b means: if there were several bins, and one of the bins was labeled 'a', does it make sense to throw 'b' in there? So, for example Module#=== means the same thing as Object#kind_of? Because if you have a bin that is ..
Jörg W Mittag
... labeled "String Class" and you have a String object, it makes sense to throw it in there, right?
Jörg W Mittag
@Jorg, I can see what you mean.. it's the container's job to know whether an object belongs to it. And I can see that with ranges, but [1,2,3] === 2 yields false, so it's not a universally held convention, which is a shame. But I understand this now, so that's cool.
Interesting, I didn't know that. Apparently, Array doesn't have an implementation of ===, therefore it inherits Object#===, which is the same as Object#== (equality). I agree, Array#=== should be a membership test.
Jörg W Mittag
For containers it's cool, but for user-defined classes that metaphor doesn't really hold as well, and reversing the comparison is the way to go.