[lnkForumImage]
TotalShareware - Download Free Software

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


 

Forums >

comp.lang.ruby

Duck Typing Hash-Like Objects

Gary Wright

3/1/2007 10:25:00 PM

I often find that when writing initialize (or alternate constructors)
I want to examine the class of the arguments to decide how to
proceed. An example is Array.new, which behaves differently if it
is given an integer argument or an array argument:

Array.new 2 # [nil,nil]
Array.new [1,2] # [1,2]

These sorts of tests can be done via Class#=== or Kernel#is_a? or
Kernel#kind_of? but that can lead to artificial constraints. Using
Kernel#respond_to? seems to avoid many of those constraints.

My question is: What is the least constraining test to determine
if you've got a hash-like object? Is arg.respond_to?(:has_key?)
reasonable? At first I thought a test for :[] would be great but
that catches strings also. I'm thinking that if someone hands my
method a Hash or a HashWithIndifferentAccess or an OrderedHash or
a tree of some sort, I'd like to be able to accept all of them.

All I really want to know is "Does this object provide key/value
pair lookups via the #[] method?", but I don't want to get strings
and integers along for the ride (for example).

Gary Wright




35 Answers

James Gray

3/1/2007 10:31:00 PM

0

On Mar 1, 2007, at 4:25 PM, Gary Wright wrote:

> I often find that when writing initialize (or alternate constructors)
> I want to examine the class of the arguments to decide how to
> proceed. An example is Array.new, which behaves differently if it
> is given an integer argument or an array argument:
>
> Array.new 2 # [nil,nil]
> Array.new [1,2] # [1,2]
>
> These sorts of tests can be done via Class#=== or Kernel#is_a? or
> Kernel#kind_of? but that can lead to artificial constraints. Using
> Kernel#respond_to? seems to avoid many of those constraints.
>
> My question is: What is the least constraining test to determine
> if you've got a hash-like object? Is arg.respond_to?(:has_key?)
> reasonable?

I would check for to_hash(), then call that method on the argument to
get its Hash representation.

James Edward Gray II


Gary Wright

3/1/2007 11:23:00 PM

0

On Mar 1, 2007, at 5:30 PM, James Edward Gray II wrote:

> I would check for to_hash(), then call that method on the argument
> to get its Hash representation.
>

That might work but what if the object is an interface to some sort
of database? You don't
really want to convert the external data structure into a Hash just
to access a single item.

Gary Wright





James Gray

3/2/2007 12:03:00 AM

0

On Mar 1, 2007, at 5:22 PM, Gary Wright wrote:

> On Mar 1, 2007, at 5:30 PM, James Edward Gray II wrote:
>
>> I would check for to_hash(), then call that method on the argument
>> to get its Hash representation.
>>
>
> That might work but what if the object is an interface to some sort
> of database? You don't
> really want to convert the external data structure into a Hash just
> to access a single item.

OK, what about using Hash#fetch and trapping the IndexError for an
invalid key?

James Edward Gray II

Jacob Fugal

3/2/2007 12:21:00 AM

0

On 3/1/07, Gary Wright <gwtmp01@mac.com> wrote:
> My question is: What is the least constraining test to determine
> if you've got a hash-like object? Is arg.respond_to?(:has_key?)
> reasonable? At first I thought a test for :[] would be great but
> that catches strings also. I'm thinking that if someone hands my
> method a Hash or a HashWithIndifferentAccess or an OrderedHash or
> a tree of some sort, I'd like to be able to accept all of them.

I'd do it like this:

def foo(duck)
# if the duck claims to have keys and indexing, we'll just use it as is
unless duck.respond_to?(:keys) and duck.respond_to?(:[])
# otherwise, we'll ask it to turn itself into a hash for us
if duck.responds_to?(:to_hash)
duck = duck.to_hash
else
# not close enough to a hash...
raise ArgumentError, "want something with keys and indexing,
or that supports to_hash"
end
end
...
end

This requires the keys method though, which thinking back, I usually
don't provide in my hash-like classes. So I don't know...

Jacob Fugal

Gary Wright

3/2/2007 12:32:00 AM

0


On Mar 1, 2007, at 7:03 PM, James Edward Gray II wrote:

> On Mar 1, 2007, at 5:22 PM, Gary Wright wrote:
>
>> On Mar 1, 2007, at 5:30 PM, James Edward Gray II wrote:
>>
>>> I would check for to_hash(), then call that method on the
>>> argument to get its Hash representation.
>>>
>>
>> That might work but what if the object is an interface to some
>> sort of database? You don't
>> really want to convert the external data structure into a Hash
>> just to access a single item.
>
> OK, what about using Hash#fetch and trapping the IndexError for an
> invalid key?

Yes, I think #fetch might be a better choice, but not exactly in the
way you suggest.
I'm thinking specifically about the construction of objects such as:

class A
def initialize(arg, &b)
case
when arg.respond_to?(:nonzero?)
# do construction based on integer-like behavior
when arg.respond_to?(:fetch)
# do construction based on hash-like behavior
when arg.respond_to?(:to_str)
# do construction based on string-like behavior
else
# punt
end
end

I was going to use :[] for hash-like behavior but that doesn't sift
out Integer and Strings so
I started using :has_key?, but that seemed wrong so I posted my
question.

Your suggestion to use fetch seems promising, but ActiveRecord, for
example doesn't define
ActiveRecord::Base.fetch. The correct choice would be find for
ActiveRecord. Hash#fetch,
and Array#fetch exist, so that does permit some nice duck-typing
between those two collections.
RBtree also defines #fetch, which is convenient.

It looks like #fetch might be the best approach.

Gary Wright




James Gray

3/2/2007 12:38:00 AM

0

On Mar 1, 2007, at 6:31 PM, Gary Wright wrote:

> class A
> def initialize(arg, &b)
> case
> when arg.respond_to?(:nonzero?)
> # do construction based on integer-like behavior

Floats have nonzero?() too. I really think picking arbitrary methods
like this to find a type is a big mistake.

You're still type checking, you're just doing it in a more fragile
way. If you want to type check, use the class, I say.

If you want it to be an Integer, ask it if it can:

Integer(...) rescue # nope...

> when arg.respond_to?(:fetch)
> # do construction based on hash-like behavior

Arrays have fetch too.

> when arg.respond_to?(:to_str)
> # do construction based on string-like behavior

String(...) rescue # nope...

> else
> # punt
> end
> end

James Edward Gray II

Gary Wright

3/2/2007 1:01:00 AM

0


On Mar 1, 2007, at 7:38 PM, James Edward Gray II wrote:
> You're still type checking, you're just doing it in a more fragile
> way. If you want to type check, use the class, I say.

Yet if I test for (Hash == mystery_obj) that would not
allow someone to pass an RBTree object instead, which I think
is a very reasonable thing to allow and works just fine if
I only use #fetch.

A minimum interface to an indexable collection might be:

has_key?(key)
fetch(key)
store(key, val)

In a quick look it seems like only Hash and RBTree implement
those methods though.




Gary Wright




Ken Bloom

3/2/2007 2:08:00 AM

0

On Fri, 02 Mar 2007 10:01:06 +0900, Gary Wright wrote:

> On Mar 1, 2007, at 7:38 PM, James Edward Gray II wrote:
>> You're still type checking, you're just doing it in a more fragile way.
>> If you want to type check, use the class, I say.
>
> Yet if I test for (Hash == mystery_obj) that would not allow someone to
> pass an RBTree object instead, which I think is a very reasonable thing
> to allow and works just fine if I only use #fetch.
>
> A minimum interface to an indexable collection might be:
>
> has_key?(key)
> fetch(key)
> store(key, val)
>
> In a quick look it seems like only Hash and RBTree implement those
> methods though.

Is there a good reason why you can't just use different constructors for
different types of objects, then just trust that they duck-type OK?

--Ken

--
Ken Bloom. PhD candidate. Linguistic Cognition Laboratory.
Department of Computer Science. Illinois Institute of Technology.
http://www.iit.edu...

dblack

3/2/2007 2:22:00 AM

0

dblack

3/2/2007 2:27:00 AM

0