surprising: class A; end; A === A ==> false

Discussion in 'Ruby' started by Sam Roberts, Feb 23, 2005.

  1. Sam Roberts

    Sam Roberts Guest

    I'm used to thinking of === being MORE useful
    On thinking about it, I can see why: in A === A, A is of class Class,
    and that class is not derived from the class A, so comparison is
    false...

    But this causes me some trouble, because there is no way to use
    case statements with a class:

    class A
    Ordinal = 1
    end
    class B
    Ordinal = 2
    end

    t = A

    case t
    when A then ...
    when B then ...
    end

    No case will ever match!

    Is my only way:

    if t == A
    ...
    elsif t == B
    ...
    elsif
    .....

    or is there some clever workaround?

    I thought of

    t = A

    case t.new
    when A then ...


    But in my case, A and B actually have initialize methods, and they
    require args (different args).


    Thanks,
    Sam

    --
    Sam Roberts <>
    Sam Roberts, Feb 23, 2005
    #1
    1. Advertising

  2. Sam Roberts

    Jamis Buck Guest

    On 01:24 Thu 24 Feb , Sam Roberts wrote:
    > I'm used to thinking of === being MORE useful
    > On thinking about it, I can see why: in A === A, A is of class Class,
    > and that class is not derived from the class A, so comparison is
    > false...
    >
    > But this causes me some trouble, because there is no way to use
    > case statements with a class:
    >
    > class A
    > Ordinal = 1
    > end
    > class B
    > Ordinal = 2
    > end
    >
    > t = A
    >
    > case t
    > when A then ...
    > when B then ...
    > end
    >
    > No case will ever match!
    >
    > Is my only way:
    >
    > if t == A
    > ...
    > elsif t == B
    > ...
    > elsif
    > .....
    >
    > or is there some clever workaround?


    You could do:

    case t.name
    when "A" then ...
    when "B" then ...
    end

    or

    case t.id
    when A.id then ...
    when B.id then ...
    end

    Still kind of kludgy, though.

    - Jamis

    --
    Jamis Buck

    http://jamis.jamisbuck.org
    ------------------------------
    "I am Victor of Borge. You will be assimil-nine-ed."
    Jamis Buck, Feb 23, 2005
    #2
    1. Advertising

  3. "Sam Roberts" <> schrieb im Newsbeitrag
    news:...
    > I'm used to thinking of === being MORE useful
    > On thinking about it, I can see why: in A === A, A is of class Class,
    > and that class is not derived from the class A, so comparison is
    > false...
    >
    > But this causes me some trouble, because there is no way to use
    > case statements with a class:
    >
    > class A
    > Ordinal = 1
    > end
    > class B
    > Ordinal = 2
    > end
    >
    > t = A
    >
    > case t
    > when A then ...
    > when B then ...
    > end
    >
    > No case will ever match!
    >
    > Is my only way:
    >
    > if t == A
    > ...
    > elsif t == B
    > ...
    > elsif
    > .....
    >
    > or is there some clever workaround?
    >
    > I thought of
    >
    > t = A
    >
    > case t.new
    > when A then ...
    >
    >
    > But in my case, A and B actually have initialize methods, and they
    > require args (different args).


    Nah, this is better (define a criterion):

    >> class A;end

    => nil
    >> classA = Object.new

    => #<Object:0x1018a900>
    >> def classA.===(x) x == A end

    => nil
    >> case A
    >> when classA
    >> puts "yes"
    >> end

    yes
    => nil

    Of course you could encapsulate all this in a class.

    Kind regards

    robert
    Robert Klemme, Feb 23, 2005
    #3
  4. Sam Roberts

    Jim Weirich Guest

    Jamis Buck said:
    > On 01:24 Thu 24 Feb , Sam Roberts wrote:
    >> Is my only way:

    [... code elided ...]
    >> or is there some clever workaround?

    >
    > You could do:
    >
    > case t.name
    > when "A" then ...
    > when "B" then ...
    > end
    >
    > or
    >
    > case t.id
    > when A.id then ...
    > when B.id then ...
    > end
    >
    > Still kind of kludgy, though.


    You could do ...

    class A
    def do_something() ... end
    end
    class B
    def do_something() ... end
    end

    t.do_something

    --
    -- Jim Weirich http://onestepback.org
    -----------------------------------------------------------------
    "Beware of bugs in the above code; I have only proved it correct,
    not tried it." -- Donald Knuth (in a memo to Peter van Emde Boas)
    Jim Weirich, Feb 23, 2005
    #4
  5. Re: surprising: class A; end; A === A ==> false

    On Thu, 24 Feb 2005 02:38:35 +0900, Jim Weirich <> wrote:
    >
    > Jamis Buck said:
    > > On 01:24 Thu 24 Feb , Sam Roberts wrote:
    > >> Is my only way:

    > [... code elided ...]
    > >> or is there some clever workaround?

    > >
    > > You could do:
    > >
    > > case t.name
    > > when "A" then ...
    > > when "B" then ...
    > > end
    > >
    > > or
    > >
    > > case t.id
    > > when A.id then ...
    > > when B.id then ...
    > > end
    > >
    > > Still kind of kludgy, though.

    >
    > You could do ...
    >
    > class A
    > def do_something() ... end
    > end
    > class B
    > def do_something() ... end
    > end
    >
    > t.do_something
    >

    Surprising, that the ruby way comes up at last. Quack ;)

    --
    Brian Schröder
    http://ruby.brian-schroeder.de/
    Brian Schröder, Feb 23, 2005
    #5
  6. Re: surprising: class A; end; A === A ==> false

    On Wed, 23 Feb 2005 18:44:20 +0100, Brian Schröder <> wrote:
    > On Thu, 24 Feb 2005 02:38:35 +0900, Jim Weirich <> wrote:
    > >
    > > Jamis Buck said:
    > > > On 01:24 Thu 24 Feb , Sam Roberts wrote:
    > > >> Is my only way:

    > > [... code elided ...]
    > > >> or is there some clever workaround?
    > > >
    > > > You could do:
    > > >
    > > > case t.name
    > > > when "A" then ...
    > > > when "B" then ...
    > > > end
    > > >
    > > > or
    > > >
    > > > case t.id
    > > > when A.id then ...
    > > > when B.id then ...
    > > > end
    > > >
    > > > Still kind of kludgy, though.

    > >
    > > You could do ...
    > >
    > > class A
    > > def do_something() ... end
    > > end
    > > class B
    > > def do_something() ... end
    > > end
    > >
    > > t.do_something
    > >

    > Surprising, that the ruby way comes up at last. Quack ;)


    The above shall read: came up in the last message. I'm not shure if I
    haven't said something completely different. Sorry that I have to
    follow up my superfluous no-advice-message with another one correcting
    it.

    Think first, then type then think then send!

    Regards,

    Brian

    --
    Brian Schröder
    http://ruby.brian-schroeder.de/
    Brian Schröder, Feb 23, 2005
    #6
  7. Sam Roberts

    Sam Roberts Guest

    Quoting , on Thu, Feb 24, 2005 at 02:38:35AM +0900:
    >
    > Jamis Buck said:
    > > On 01:24 Thu 24 Feb , Sam Roberts wrote:
    > >> Is my only way:

    > [... code elided ...]
    > > case t.id
    > > when A.id then ...
    > > when B.id then ...
    > > end
    > >
    > > Still kind of kludgy, though.


    I think thats the best suggestion so far.

    > You could do ...
    >
    > class A
    > def do_something() ... end
    > end
    > class B
    > def do_something() ... end
    > end
    >
    > t.do_something


    I know where you are coming from, this is a nice pattern, but in this
    case t is a return value of Resolv::DNS::Message#question, and I have to
    answer the question. How I answer the question depends on the question
    (of course!), but is not part of the behaviour of the question,
    different "answers" answer the question in different ways.

    Thanks for all the suggestions, folks.

    Sam
    Sam Roberts, Feb 24, 2005
    #7
  8. Hi,

    Am Donnerstag, 24. Feb 2005, 11:58:09 +0900 schrieb Sam Roberts:
    > Quoting , on Thu, Feb 24, 2005 at 02:38:35AM +0900:
    > > You could do ...
    > >
    > > class A
    > > def do_something() ... end
    > > end
    > > class B
    > > def do_something() ... end
    > > end
    > >
    > > t.do_something


    Of course.

    > I know where you are coming from, this is a nice pattern, but in this
    > case t is a return value of Resolv::DNS::Message#question, and I have to
    > answer the question. How I answer the question depends on the question
    > (of course!), but is not part of the behaviour of the question,
    > different "answers" answer the question in different ways.


    I accept that. So I try:

    $ ruby -e 'class Class ; def === oth ; self == oth ; end ; end ;
    > class C ; end ; p C === C'

    true

    But this one crashes:

    $ irb
    irb(main):001:0> class Class ; def === oth ; self == oth ; end ; end
    => nil
    irb(main):002:0> class C ; end
    /usr/local/lib/ruby/1.9/irb/ruby-token.rb:101:in `Token': undefined method `ancestors' for ";":String (NoMethodError)
    ...

    Is this the behaviour to be expected?

    Bertram


    --
    Bertram Scharpf
    Stuttgart, Deutschland/Germany
    http://www.bertram-scharpf.de
    Bertram Scharpf, Feb 24, 2005
    #8
  9. Bertram Scharpf <> wrote:
    >
    > $ ruby -e 'class Class ; def === oth ; self == oth ; end ; end ;
    > > class C ; end ; p C === C'

    > true


    Not sure about the crash, but this is probably more useful behaviour:

    class Class
    alias :=== :caseeq

    def ===(other)
    (self == other) || self.caseeq(other)
    end
    end

    martin
    Martin DeMello, Feb 24, 2005
    #9
  10. Sam Roberts

    Jim Weirich Guest

    On Wednesday 23 February 2005 09:58 pm, Sam Roberts wrote:
    > > You could do ...
    > >
    > > class A
    > > def do_something() ... end
    > > end
    > > class B
    > > def do_something() ... end
    > > end
    > >
    > > t.do_something

    >
    > I know where you are coming from, this is a nice pattern, but in this
    > case t is a return value of Resolv::DNS::Message#question, and I have to
    > answer the question. How I answer the question depends on the question
    > (of course!), but is not part of the behaviour of the question,
    > different "answers" answer the question in different ways.


    I'm not sure if you are saying that won't work because (1) the do_something
    method is not part of the behavior of the classes in question, or (2) you
    need to respond differently to these classes in different circumstances. Or
    perhaps both (1) and (2) are the case.

    If (1), then remember that you can always open up any class and add more
    behavior.

    If (2), then you can do something like the following (simple visitor pattern):

    module Kernel
    def accept_visitor(visitor)
    visitor.send("visit_" + self.class.name.downcase.gsub(/::/, '_'), self)
    end
    end

    class A; end
    class B; end

    class MySpecialVisitor
    def visit_a(a)
    puts "Doing something with A: (#{a})"
    end
    def visit_b(b)
    puts "Doing something else with B: (#{b})"
    end
    end

    A.new.accept_visitor(MySpecialVisitor.new)
    B.new.accept_visitor(MySpecialVisitor.new)

    You can define as many visitors as you need to get the varied behaviors
    required by your problem.

    The solution is a bit more complicated, but allows open-ended behaviors for
    any class.

    --
    -- Jim Weirich http://onestepback.org
    -----------------------------------------------------------------
    "Beware of bugs in the above code; I have only proved it correct,
    not tried it." -- Donald Knuth (in a memo to Peter van Emde Boas)
    Jim Weirich, Feb 24, 2005
    #10
  11. Sam Roberts

    Sam Roberts Guest

    Quoting , on Thu, Feb 24, 2005 at 03:18:41PM +0900:
    > On Wednesday 23 February 2005 09:58 pm, Sam Roberts wrote:
    > > > You could do ...
    > > >
    > > > class A
    > > > def do_something() ... end
    > > > end
    > > > class B
    > > > def do_something() ... end
    > > > end
    > > >
    > > > t.do_something

    > >
    > > I know where you are coming from, this is a nice pattern, but in this
    > > case t is a return value of Resolv::DNS::Message#question, and I have to
    > > answer the question. How I answer the question depends on the question
    > > (of course!), but is not part of the behaviour of the question,
    > > different "answers" answer the question in different ways.

    >
    > I'm not sure if you are saying that won't work because (1) the do_something
    > method is not part of the behavior of the classes in question, or (2) you
    > need to respond differently to these classes in different circumstances. Or
    > perhaps both (1) and (2) are the case.
    >
    > If (1), then remember that you can always open up any class and add more
    > behavior.


    Isn't part, and *shouldn't* be part.

    > If (2), then you can do something like the following (simple visitor pattern):


    Nice trick, but all it has the opposite effect (decreased code
    maintainability) than using a case, in my case.

    The reason you suggest this, I assume, is that switching on input type
    often means that you've put logic in the wrong place, often the switch
    ends up being in many places, and when you add a new input type you have
    to find all the 'handling' code, and upgrade it.

    However, I don't have this problem, I'm actually using a visitor pattern
    already, and I've encapsulated knowledge in what (I think) is the right
    place.

    I'm working on a mDNS responder, and this part looks like:

    question_msg = DNS::Message.decode(@sock.recv)

    answer_msg = DNS::Message.new

    question_msg.each_question do |question|
    @services.each do |service|
    service.answer_question(question, answer_msg)
    end
    end

    @sock.send(answer_msg.encode)


    My loop doesn't know what kinds of questions there are, doesn't know
    what kind of answers there are, or how the questions are to be answered.

    Each service has the opportunity to add an answer to the outgoing
    message, if it wants to.

    It's in the Service class that I need the case:

    class Service

    def answer_question(question, msg)
    case question.type
    when DNS::Resource::pTR
    # ... I know an answer to that..

    ...

    end
    end
    end


    Adding yet another class (MySpecialVisitor) would mean I'm removing the
    knowledge from the Service of what questions it can answer, it would
    become a dumb container with getter/setter types functions, and this new
    class would have all the knowledge. It would be worth it only if there
    were multiple ways to answer questions for a given service... but
    there's not.

    Thanks for the very interesting code snippet, I may indeed use that
    approach one of these days!

    Cheers,
    Sam
    Sam Roberts, Feb 25, 2005
    #11
  12. Sam Roberts

    Jim Weirich Guest

    On Thursday 24 February 2005 10:50 pm, Sam Roberts wrote:
    > > If (1), then remember that you can always open up any class and add more
    > > behavior.

    >
    > Isn't part, and *shouldn't* be part.


    Because the services are open ended ... got it.

    [... visitor pattern elided ...]

    > The reason you suggest this, [...] when you add a new input type you have
    > to find all the 'handling' code, and upgrade it.
    >
    > However, I don't have this problem, I'm actually using a visitor pattern
    > already, and I've encapsulated knowledge in what (I think) is the right
    > place.


    And the services only respond to the subset of the questions they know, so
    there is no problem with adding new questions and having to update all the
    services to know about the new question. Ok.

    Thanks for the code snippets. They really helped make the problem clear.

    I've got one more variation to toss in your direction. I don't know if this
    is any better than what you already have, but I found it to be
    interesting ...

    The basic twist is that it uses method names to represent the question (rather
    than the type of the question object) and the question object asks the
    service if it can answer the question. Here's the code ...

    # BEGIN ========================================
    # Generic question class. Actual questions derive from this class and
    # define their specific question with the +consider+ command.
    class Question
    def self.consider(sym)
    define_method:)ask) do |service, answers|
    if service.respond_to? sym
    service.send(sym, answers)
    end
    end
    end
    end

    # Specific Questions I might need answered in the morning.

    class RainingQuestion < Question
    consider :is_raining
    end

    class CloudyQuestion < Question
    consider :is_cloudy
    end

    class JamQuestion < Question
    consider :traffic_report
    end

    class ClothingQuestion < Question
    consider :are_my_socks_matching
    end

    # Now some services that might (or might not) answer some
    # (or all) of my questions

    class WeatherService
    def is_raining(answers)
    answers << "It is raining."
    end
    def is_cloudy(answers)
    answers << "It is cloudy."
    end
    end

    class TrafficService
    def traffic_report(answers)
    answers << "No Traffic Jams."
    end
    end

    # Suppert functions and main loop

    def read_questions
    [RainingQuestion.new, JamQuestion.new, CloudyQuestion.new]
    end

    def send_answers(answers)
    puts answers
    end

    my_services = [WeatherService.new, TrafficService.new]
    questions = read_questions
    answers = []
    questions.each do |question|
    my_services.each do |service|
    question.ask(service, answers)
    end
    end
    send_answers(answers)
    # END ===============================

    I'm not sure if I like using methods as negotiator between answers and
    questions. It works pretty well, but it does add an arbitrary piece of
    information into the mix.

    Hmmm ... another idea would to take the original code and use a hash map
    instead of a case statement. The services would lookup the question class in
    a hash map and execute any blocks found there.

    Well, thanks for indulging me. I've found this puzzle to be as enjoyable as
    the Weekly Ruby Quiz.

    --
    -- Jim Weirich http://onestepback.org
    -----------------------------------------------------------------
    "Beware of bugs in the above code; I have only proved it correct,
    not tried it." -- Donald Knuth (in a memo to Peter van Emde Boas)
    Jim Weirich, Feb 25, 2005
    #12
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Srinu
    Replies:
    6
    Views:
    260
    Stefan van Kessel
    Aug 19, 2010
  2. Stefan Behnel
    Replies:
    2
    Views:
    191
    Gregory Ewing
    Aug 16, 2011
  3. trans.  (T. Onoma)
    Replies:
    0
    Views:
    151
    trans. (T. Onoma)
    Sep 25, 2004
  4. trans.  (T. Onoma)
    Replies:
    1
    Views:
    165
    David A. Black
    Sep 25, 2004
  5. Iñaki Baz Castillo
    Replies:
    9
    Views:
    193
    Igor Pirnovar
    Feb 26, 2009
Loading...

Share This Page