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

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 <sroberts@certicom.com>

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

···

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?

--
Jamis Buck
jamis_buck@byu.edu
http://jamis.jamisbuck.org
------------------------------
"I am Victor of Borge. You will be assimil-nine-ed."

Jamis Buck said:

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

···

On 01:24 Thu 24 Feb , Sam Roberts wrote:

--
-- Jim Weirich jim@weirichhouse.org 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)

Surprising, that the ruby way comes up at last. Quack :wink:

···

On Thu, 24 Feb 2005 02:38:35 +0900, Jim Weirich <jim@weirichhouse.org> 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

--
Brian Schröder
http://ruby.brian-schroeder.de/

Quoting jim@weirichhouse.org, on Thu, Feb 24, 2005 at 02:38:35AM +0900:

Jamis Buck said:
>> 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

···

> On 01:24 Thu 24 Feb , Sam Roberts wrote:

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

···

On Wed, 23 Feb 2005 18:44:20 +0100, Brian Schröder <ruby.brian@gmail.com> wrote:

On Thu, 24 Feb 2005 02:38:35 +0900, Jim Weirich <jim@weirichhouse.org> 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 :wink:

--
Brian Schröder
http://ruby.brian-schroeder.de/

Hi,

Quoting jim@weirichhouse.org, 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

···

Am Donnerstag, 24. Feb 2005, 11:58:09 +0900 schrieb Sam Roberts:

--
Bertram Scharpf
Stuttgart, Deutschland/Germany
http://www.bertram-scharpf.de

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.

···

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.

--
-- Jim Weirich jim@weirichhouse.org 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)

Quoting jim@weirichhouse.org, on Thu, Feb 24, 2005 at 03:18:41PM +0900:

> > 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

···

On Wednesday 23 February 2005 09:58 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.

···

On Thursday 24 February 2005 10:50 pm, Sam Roberts wrote:

--
-- Jim Weirich jim@weirichhouse.org 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)