Tricky problem with ===


#1

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


#2

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

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


#3

On 27.05.2007 07:04, Morton G. wrote:

undef_method m unless m =~ /^__/
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?

something like a graph node, or linked list node (etc). Anyway, the
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…
:slight_smile:

Kind regards

robert


#4

On 5/27/07, Robert K. 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 G. 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… :slight_smile:

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


#5

Erwin A. 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


#6

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

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 Z.
– Lead Rails Evangelist
– removed_email_address@domain.invalid
– Engine Y., Serious Rails Hosting
– (866) 518-YARD (9273)


#7

Ezra Z. 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


#8

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

Stefan


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

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

Thanks
– Ezra


#9

Hi –

On Sun, 27 May 2007, Erwin A. wrote:

when Array == x.class
up any code that doesn’t know about it… it should seem completely
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. Are you sure a mixin wouldn’t make more sense?

David


#10

On 27.05.2007 15:23, Erwin A. wrote:

On 5/27/07, Robert K. 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?

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. :slight_smile: I’d also consider mixins or other mechanisms. It all
depends on what problem you are trying to solve…

Kind regards

robert


#11

On 5/27/07, removed_email_address@domain.invalid removed_email_address@domain.invalid 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


#12

Hi –

On Mon, 28 May 2007, Erwin A. wrote:

time. It’s job is to return true if the given time is more than a week
and look up the documentation. I agree distinguishing objects by class
#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.

I was thinking of something like (semi-tested only):

module DateComp
def <(other)
Time.parse(strftime("%m/%d/%y")) < other
end
end

d = Date.new.extend(DateComp)
your_method(d)

That way, you can use duck typing in the method itself, which means
that you don’t have to change the method if you want to provide it
with other objects; you just have to prepare things on the calling
end. You might have to have a more complex mixin for your proxy case,
but anyway, that’s the kind of thing I was thinking about.

David


#13

unknown wrote:

Hi –

On Mon, 28 May 2007, Erwin A. wrote:

time. It’s job is to return true if the given time is more than a week
and look up the documentation. I agree distinguishing objects by class
#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.

I was thinking of something like (semi-tested only):

module DateComp
def <(other)
Time.parse(strftime("%m/%d/%y")) < other
end
end

d = Date.new.extend(DateComp)
your_method(d)

That way, you can use duck typing in the method itself, which means
that you don’t have to change the method if you want to provide it
with other objects; you just have to prepare things on the calling
end. You might have to have a more complex mixin for your proxy case,
but anyway, that’s the kind of thing I was thinking about.

David

Universal Date/Time comparison is indeed difficult as neither your nor
mine (below) solution attribute for different TimeZones and/or DST.
I’d implement a universal comparison like below:

def <=>(other)
[year, day_of_year, hour, minute, second] <=> [other.year,
other.day_of_year, other.hour, other.minute, other.second]
end

Regards
Stefan


#14

I think I may have come to a solution with

class Object
def self.=== other
#puts “object: #{self} == #{other}”
other.is_a? self
end
end

It may not work in some special cases, but it seems to do the trick.
It appears this is only called when comparing to an instance of Class,
so case x when 55; true; end doesn’t involve this method. I haven’t
tested this much, so I may be horribly breaking something somewhere,
but so far it seems okay.

However, I haven’t gotten it to work when comparing to a Module, so
x.extend(SomeMixin); case x when SomeMixin … doesn’t work.
Module.=== doesn’t seem to be involved in this test.

Regards,
Erwin


#15

On 5/28/07, Stefan R. removed_email_address@domain.invalid wrote:

Universal Date/Time comparison is indeed difficult as neither your nor
mine (below) solution attribute for different TimeZones and/or DST.
I’d implement a universal comparison like below:

def <=>(other)
[year, day_of_year, hour, minute, second] <=> [other.year,
other.day_of_year, other.hour, other.minute, other.second]
end

I was just trying to illustrate the need for using case to determine
an object’s class. If we were to use mixins as an alternative, it’s
still very complicated because we’ll need to redefine Time#<=>,
Date#<=> and so on. Won’t we now have to check if
other.is_a?(DateComp) and still handle the cases when
other.is_a?(self.class)… ? I don’t see any difference, because now
we’re relying on knowing about DateComp instead of Time or Date.

I agree mixins are useful in many cases, but maybe not in this one.
Another way to make code less rigid might be to still use the case
construct but have something that trusts the foreign object to do the
right thing in the default case. For example

class Date
def <=> other
case other
when self.class # this case is redundant, with the default case
below
self.ajd <=> other.ajd
when Numeric
self.ajd <=> other
else
if other.respond_to? :ajd
# hope that other.ajd means the same thing
self.ajd <=> other.ajd
else
nil # causes ArgumentError (from Comparable probably)
end
end
end
end

Then any class that’s supposed to represent a date or time can
implement #ajd and be compatable. But we still had to check if other
was an Integer, because defining Numeric#ajd doesn’t really make
sense.

Regards,

Erwin


#16

Hi –

On Mon, 28 May 2007, Erwin A. wrote:

I was just trying to illustrate the need for using case to determine
an object’s class. If we were to use mixins as an alternative, it’s
still very complicated because we’ll need to redefine Time#<=>,
Date#<=> and so on. Won’t we now have to check if
other.is_a?(DateComp) and still handle the cases when
other.is_a?(self.class)… ? I don’t see any difference, because now
we’re relying on knowing about DateComp instead of Time or Date.

You don’t have to test for DateComp ancestry, though. You just assume
it (or equivalent) in your method, and then make sure that only
objects with that mixin (or equivalent) are passed to the method.
That way, you encapsulate just the operation itself in the method
(basically, duck typing), and the caller takes responsibility for only
sending along objects that fit the bill. (Ha ha, bill, duck, get it?
Oh, never mind :slight_smile:

David


#17

On 5/28/07, Robert K. removed_email_address@domain.invalid wrote:

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’m not trying to solve a business problem (I’m not sure what you
mean?). I would think this is something more like a design pattern,
like http://home.earthlink.net/~huston2/dp/proxy.html

Here’s some code that implements it

class Object
def self.=== other
#puts “object #{self} === #{other}”
other.is_a? self
end
end

class Thunk
instance_methods.each do |m|
undef_method m unless m =~ /^__/
end

def initialize &block
@proc = block
@computed = false
end

def method_missing method, *args, &block
unless @computed
puts “computing value”
@computed = true
@value = @proc.call
end
@value.send method, *args, &block
end
end

$stdout.sync = true

the block isn’t called until any message is sent to x, this way

if x is never used (maybe there’s a condition that prevents this)

we’ll never have to bother performing a slow computation

x = Thunk.new { sleep 5; ‘expensive computation…’; 50000 }; nil

if Time.now.sec.modulo(2).zero? # even
puts “value: #{x}”
end

Regards,
Erwin

PS: Yes, I am aware there have been other ruby projects that do
similar things. This is just an example, where any code that’s passed
a “Thunk” doesn’t need to know it’s anything special… unless it’s
using a case statement to check if it implements a mixin :wink: