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

S

Sam Roberts

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
 
J

Jamis Buck

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
 
R

Robert Klemme

Sam Roberts said:
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):
yes
=> nil

Of course you could encapsulate all this in a class.

Kind regards

robert
 
J

Jim Weirich

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
 
B

Brian Schröder

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
Surprising, that the ruby way comes up at last. Quack ;)
 
B

Brian Schröder

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
 
S

Sam Roberts

Quoting (e-mail address removed), 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
 
B

Bertram Scharpf

Hi,

Am Donnerstag, 24. Feb 2005, 11:58:09 +0900 schrieb Sam Roberts:
Quoting (e-mail address removed), on Thu, Feb 24, 2005 at 02:38:35AM +0900:

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
 
M

Martin DeMello

Bertram Scharpf said:
$ ruby -e 'class Class ; def === oth ; self == oth ; end ; end ;
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
 
J

Jim Weirich

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

Sam Roberts

Quoting (e-mail address removed), on Thu, Feb 24, 2005 at 03:18:41PM +0900:
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
 
J

Jim Weirich

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.
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,756
Messages
2,569,535
Members
45,008
Latest member
obedient dusk

Latest Threads

Top