[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Tricky problem with ===

Erwin Abbott

5/27/2007 3:13:00 AM

I have a "BlankState" type of class and it's method_missing forwards
everything to an instance variable. This way I can treat the object
specially in my own code and all other code doesn't need to know the
difference. For a contrived example:

class Proxy
# remove methods inherited by Object, etc
instance_methods.each do |m|
undef_method m unless m =~ /^__/
end

def initialize name, value
@name, @value = name, value
end

def to_myformat
"#{@name}: #{@value}"
end

def method_missing name, *args, &block
@value.send name, *args, &block
end
end

object = "regular string"
x = Proxy.new("UID", object)

x.class # => Array
x.size # => 5
x.to_myformat # => UID: regular string

While that's a pretty boring example, the idea might come in handy for
something like a graph node, or linked list node (etc). Anyway, the
problem I have is the way this works:

case x
when Array puts "Array"
when Proxy puts "Proxy"
end

This calls Array.===(x) which doesn't call x.class, and it returns
false. If I redefine Array.===(other) to test self == other.class then
it works. I can't think of any way to get this to work since it
doesn't appear there are any methods of x being called that I can
intercept. Does ruby get the class 'internally' instead of calling
x.class? I guess if worse comes to worse I can use if x.is_a? Array;
...; elsif x.is_a? Foo; ...; end. I don't want to go about redefining
every class's === method, that seems to be a mess.

Erwin

16 Answers

Morton Goldberg

5/27/2007 5:04:00 AM

0

On May 26, 2007, at 11:13 PM, Erwin Abbott wrote:

> I have a "BlankState" type of class and it's method_missing forwards
> everything to an instance variable. This way I can treat the object
> specially in my own code and all other code doesn't need to know the
> difference. For a contrived example:
>
> class Proxy
> # remove methods inherited by Object, etc
> instance_methods.each do |m|
> undef_method m unless m =~ /^__/
> end
>
> def initialize name, value
> @name, @value = name, value
> end
>
> def to_myformat
> "#{@name}: #{@value}"
> end
>
> def method_missing name, *args, &block
> @value.send name, *args, &block
> end
> end
>
> object = "regular string"
> x = Proxy.new("UID", object)
>
> x.class # => Array
> x.size # => 5
> x.to_myformat # => UID: regular string

What I see when I evaluate your code is:

x.class # => String
x.size # => 14

> While that's a pretty boring example, the idea might come in handy for
> something like a graph node, or linked list node (etc). Anyway, the
> problem I have is the way this works:
>
> case x
> when Array puts "Array"
> when Proxy puts "Proxy"
> end

If you really have to do this kind of thing, a work-around you might
try is:

result = case x.class.new
when Array then "Array"
when String then "String"
when Proxy then "Proxy"
end
result # => "String"

That's kind of ugly, but then I think using case statements to
distinguish the class of an object is kind of ugly under any
circumstances.

Regards, Morton



Robert Klemme

5/27/2007 9:09:00 AM

0

On 27.05.2007 07:04, Morton Goldberg wrote:
> On May 26, 2007, at 11:13 PM, Erwin Abbott wrote:
>
>> I have a "BlankState" type of class and it's method_missing forwards
>> everything to an instance variable. This way I can treat the object
>> specially in my own code and all other code doesn't need to know the
>> difference. For a contrived example:
>>
>> class Proxy
>> # remove methods inherited by Object, etc
>> instance_methods.each do |m|
>> undef_method m unless m =~ /^__/
>> end
>>
>> def initialize name, value
>> @name, @value = name, value
>> end
>>
>> def to_myformat
>> "#{@name}: #{@value}"
>> end
>>
>> def method_missing name, *args, &block
>> @value.send name, *args, &block
>> end
>> end
>>
>> object = "regular string"
>> x = Proxy.new("UID", object)

Erwin, are you aware of delegate?

>> x.class # => Array
>> x.size # => 5
>> x.to_myformat # => UID: regular string
>
> What I see when I evaluate your code is:
>
> x.class # => String
> x.size # => 14
>
>> While that's a pretty boring example, the idea might come in handy for
>> something like a graph node, or linked list node (etc). Anyway, the
>> problem I have is the way this works:
>>
>> case x
>> when Array puts "Array"
>> when Proxy puts "Proxy"
>> end
>
> If you really have to do this kind of thing, a work-around you might try
> is:
>
> result = case x.class.new
> when Array then "Array"
> when String then "String"
> when Proxy then "Proxy"
> end
> result # => "String"
>
> That's kind of ugly, but then I think using case statements to
> distinguish the class of an object is kind of ugly under any circumstances.

It's not only ugly it probably also does not make much sense because the
OP wants to test "x" and not "x.class.new".

A better solution would be to use the second form of "case" to have more
control over the test

case
when Array == x.class
when String == x.class
end

An alternative way is to build custom testers:

ARRAY = lambda {|x| Array === x.class}
def ARRAY.===(o) self[o] end

case x
when ARRAY
....
end

Viewing this more abstract: maybe case isn't even the right thing to do.
We would have to know more about what the OP is trying to accomplish.

On an even more general level there is a certain contradiction between
having something behave exactly like an Array - but also different... :-)

Kind regards

robert

Erwin Abbott

5/27/2007 1:24:00 PM

0

On 5/27/07, Robert Klemme wrote:
> Erwin, are you aware of delegate?

Yes, but it's not transparent as I'd like. I don't want the delegating
class to answer methods like class and is_a? for example. It also
doesn't work when trying to distinguish the class with a case
construct.

On 27.05.2007 07:04, Morton Goldberg wrote:
> What I see when I evaluate your code is:
>
> x.class # => String
> x.size # => 14

My mistake, I had an array to begin with and didn't remember to change
the results to match when I changed it to a string.

> A better solution would be to use the second form of "case" to have more
> control over the test
>
> case
> when Array == x.class
> when String == x.class
> end

That's not bad. I like it... and it's a little more explicit, too. I
didn't know you could use case that way, that seems pretty useful.

> Viewing this more abstract: maybe case isn't even the right thing to do.
> We would have to know more about what the OP is trying to accomplish.

Well the idea is to wrap an object with this proxy class and not mess
up any code that doesn't know about it... it should seem completely
transparent. It gives you a separate namespace for methods and
instance variables, so you don't have to worry about redefining those
with a mixin or singleton method, but you can still specialize some
object by wrapping it.

> I think using case statements to distinguish the class of an object is
> kind of ugly under any circumstances.

I think it works pretty well, but I'd like to know what you prefer
instead. I realize distinguishing objects by class is not encouraged,
but I find myself needing it often, for better or worse.

> On an even more general level there is a certain contradiction between
> having something behave exactly like an Array - but also different... :-)

Yes, that's a good observation. Though I'm very happy with how close
I've gotten to making it happen.

Stefan Rusterholz

5/27/2007 2:24:00 PM

0

Erwin Abbott wrote:
> case x
> when Array puts "Array"
> when Proxy puts "Proxy"
> end

Ugly, but you could try:
puts case [x.class]
when [Array]: "Array"
when [Proxy]: "Proxy"
else "Hu?"
end

Regards
Stefan

--
Posted via http://www.ruby-....

Ezra Zygmuntowicz

5/27/2007 8:34:00 PM

0


On May 27, 2007, at 7:24 AM, Stefan Rusterholz wrote:

> Erwin Abbott wrote:
>> case x
>> when Array puts "Array"
>> when Proxy puts "Proxy"
>> end
>
> Ugly, but you could try:
> puts case [x.class]
> when [Array]: "Array"
> when [Proxy]: "Proxy"
> else "Hu?"
> end
>
> Regards
> Stefan


case uses === to match when clauses. Therefor you can do it like this:

case x
when Array
puts "Array"
when Proxy
puts "Proxy
end

Cheers-

-- Ezra Zygmuntowicz
-- Lead Rails Evangelist
-- ez@engineyard.com
-- Engine Yard, Serious Rails Hosting
-- (866) 518-YARD (9273)



Stefan Rusterholz

5/27/2007 10:49:00 PM

0

Ezra Zygmuntowicz wrote:
> case uses === to match when clauses. Therefor you can do it like this:

Which doesn't work for him because his class isn't really an Array, just
pretends to be. He outlined that in his initial post.
If I misunderstood his initial post, feel free to correct me.

Regards
Stefan

--
Posted via http://www.ruby-....

Ezra Zygmuntowicz

5/27/2007 10:54:00 PM

0


On May 27, 2007, at 3:49 PM, Stefan Rusterholz wrote:

> Ezra Zygmuntowicz wrote:
>> case uses === to match when clauses. Therefor you can do it like
>> this:
>
> Which doesn't work for him because his class isn't really an Array,
> just
> pretends to be. He outlined that in his initial post.
> If I misunderstood his initial post, feel free to correct me.
>
> Regards
> Stefan
>
> --
> Posted via http://www.ruby-....
>

Yeah I think i jumped in without reading what he really wanted. My
apologies.

Thanks
-- Ezra


dblack

5/27/2007 11:07:00 PM

0

Erwin Abbott

5/28/2007 3:08:00 AM

0

On 5/27/07, dblack@wobblini.net <dblack@wobblini.net> wrote:
>> I think it works pretty well, but I'd like to know what you prefer
>> instead. I realize distinguishing objects by class is not encouraged,
>> but I find myself needing it often, for better or worse.
>
> But you're running aground on exactly why it isn't encouraged: namely,
> it doesn't give you definitive information about the object's behavior
> or interface.

Say you have some method that accepts various objects that represent a
time. It's job is to return true if the given time is more than a week
old. We have DateTime, Time, Date, and probably some more I don't know
about. We can test that these objects have a comparison operator, and
not pay attention to their class, but that doesn't help me because
Date.new < Time.now doesn't work. In fact, Date's <=> checks if it was
passed an Integer or a Date object, because knowing the object's class
is necesarry to know how to compare them.

I don't see any other way to write that method, am I missing
something? It seems the most definitive information about any objects
behavior is its source code, next to that we can ask what class it is
and look up the documentation. I agree distinguishing objects by class
has its problems, like Date#<=> doesn't work with anything but Dates
and Integers (when maybe it should be testing respond_to? :ajd when it
isn't given an Integer), but is there a better alternative?

> Are you sure a mixin wouldn't make more sense?

Instead of using a proxy? It's a trade-off at this point between
getting case to work and having to write code that can't use intstance
variable names like @id or @name because the original object might
already be using them, or having to alias an existing #name as
#old_name and making sure my #name doesn't break any code in that
class. I might still break something with a proxy class answering
#name, but it won't break the underlying object when it calls name on
itself. I think at this point I'll accept that case statements won't
work, but I'm going to try a few other things before giving up.

Regards,
Erwin

Robert Klemme

5/28/2007 9:17:00 AM

0

On 27.05.2007 15:23, Erwin Abbott wrote:
> On 5/27/07, Robert Klemme wrote:
>> Viewing this more abstract: maybe case isn't even the right thing to do.
>> We would have to know more about what the OP is trying to accomplish.
>
> Well the idea is to wrap an object with this proxy class and not mess
> up any code that doesn't know about it... it should seem completely
> transparent. It gives you a separate namespace for methods and
> instance variables, so you don't have to worry about redefining those
> with a mixin or singleton method, but you can still specialize some
> object by wrapping it.

Yes, you said so earlier. But what business problem are you trying to
solve?

>> I think using case statements to distinguish the class of an object is
>> kind of ugly under any circumstances.
>
> I think it works pretty well, but I'd like to know what you prefer
> instead. I realize distinguishing objects by class is not encouraged,
> but I find myself needing it often, for better or worse.
>
>> On an even more general level there is a certain contradiction between
>> having something behave exactly like an Array - but also different... :-)
>
> Yes, that's a good observation. Though I'm very happy with how close
> I've gotten to making it happen.

The problem is that it seems to be a fine line between coming close and
breaking. :-) I'd also consider mixins or other mechanisms. It all
depends on what problem you are trying to solve...

Kind regards

robert