Forum: Ruby Duck Typing Hash-Like Objects

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Gary W. (Guest)
on 2007-03-02 00:25
(Received via mailing list)
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 W.
James G. (Guest)
on 2007-03-02 00:32
(Received via mailing list)
On Mar 1, 2007, at 4:25 PM, Gary W. wrote:

> 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 G. II
Gary W. (Guest)
on 2007-03-02 01:23
(Received via mailing list)
On Mar 1, 2007, at 5:30 PM, James Edward G. 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 W.
James G. (Guest)
on 2007-03-02 02:03
(Received via mailing list)
On Mar 1, 2007, at 5:22 PM, Gary W. wrote:

> On Mar 1, 2007, at 5:30 PM, James Edward G. 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 G. II
Jacob F. (Guest)
on 2007-03-02 02:21
(Received via mailing list)
On 3/1/07, Gary W. <removed_email_address@domain.invalid> 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 F.
Gary W. (Guest)
on 2007-03-02 02:32
(Received via mailing list)
On Mar 1, 2007, at 7:03 PM, James Edward G. II wrote:

>> 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 W.
James G. (Guest)
on 2007-03-02 02:39
(Received via mailing list)
On Mar 1, 2007, at 6:31 PM, Gary W. 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 G. II
Gary W. (Guest)
on 2007-03-02 03:02
(Received via mailing list)
On Mar 1, 2007, at 7:38 PM, James Edward G. 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 W.
Ken B. (Guest)
on 2007-03-02 04:11
(Received via mailing list)
On Fri, 02 Mar 2007 10:01:06 +0900, Gary W. wrote:

>    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
unknown (Guest)
on 2007-03-02 04:23
(Received via mailing list)
Hi --

On Fri, 2 Mar 2007, Jacob F. wrote:

> def foo(duck)
>     end
>   end
>   ...
> end

Or you could just do:

   duck[whatever]....

and rescue the exception(s), possibly cascading down into a to_hash
operation.  You might as well fail without bothering with the
respond_to? calls -- just ask the object to do what it's supposed to,
and handle the error cases.


David
unknown (Guest)
on 2007-03-02 04:27
(Received via mailing list)
Hi --

On Fri, 2 Mar 2007, Gary W. wrote:

> On Mar 1, 2007, at 7:38 PM, James Edward G. 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.

I had the impression James was talking about the Integer and String
methods, though then again those aren't actually the classes.  So I'm
not sure what he meant :-)  But I don't think it was just to test
class membership, since that manifestly doesn't help in the kind of
situation you're describing.


David
James G. (Guest)
on 2007-03-02 04:46
(Received via mailing list)
On Mar 1, 2007, at 8:26 PM, removed_email_address@domain.invalid wrote:

>> is a very reasonable thing to allow and works just fine if
>> I only use #fetch.
>
> I had the impression James was talking about the Integer and String
> methods, though then again those aren't actually the classes.  So I'm
> not sure what he meant :-)

I was probably just babbling, not making sense.  I do that.

> But I don't think it was just to test class membership, since that
> manifestly doesn't help in the kind of situation you're describing.

Yeah, you're right.  I was feeling that this is just an attempt to
sidestep type checking by inventing a clever new type checking
system.  It's really just trying to provide a flexible interface though.

Given that, I'm changing my answer.

This is a documentation problem.  As long as the documentation tells
me your method needs a put_stuff_in() and a pull_stuff_out() to work,
tells me what they will be passed, and *doesn't* type check, you
support ALL data structures.  I can always wrap Hash, RBTree,
Integer, JamesCustomDataVoid, or whatever in a trivial class
implementing those calls.

Am I making sense yet, or do I just need to go to sleep now?

James Edward G. II
unknown (Guest)
on 2007-03-02 04:50
(Received via mailing list)
Hi --

On Fri, 2 Mar 2007, removed_email_address@domain.invalid wrote:

>> is a very reasonable thing to allow and works just fine if
>> I only use #fetch.
>
> I had the impression James was talking about the Integer and String
> methods, though then again those aren't actually the classes.  So I'm
> not sure what he meant :-)  But I don't think it was just to test
> class membership, since that manifestly doesn't help in the kind of
> situation you're describing.

Well, I should say: it's a way to deal with some of the practicalities
of a situation where you really only want objects of certain classes,
at the expense of duck typing.  But (a) it sounds like you want
something more elastic, and (b) testing class membership doesn't tell
you anything definitive, so it doesn't solve the problem if you're
thinking that rogue objects might be coming in to the method (since if
someone can roguely send it, say, a Proc, which responds to [], they
can presumably send it a hash that responds to [] irresponsibly).

I guess I tend to think in terms of error handling: that is, let
objects call [], but catch the ones that fail, or the ones that hand
back nonsense (in the context) values.

It's funny sometimes how discussions of duck typing come at the same
thing from two directions: protecting systems from supposed gremlins
that are engineering its demise by extending objects with destructive
but well-camouflaged behaviors, and exploring the coolness of the
openness of Ruby objects.  Or something.


David
unknown (Guest)
on 2007-03-02 04:59
(Received via mailing list)
Hi --

On Fri, 2 Mar 2007, James Edward G. II wrote:

> Yeah, you're right.  I was feeling that this is just an attempt to
> sidestep type checking by inventing a clever new type checking system.

Or an attempt to sidestep class-checking by inventing a type-checking
system :-)  A few years ago there were some interesting attempts to
come up with a systematic way to determine an object's type, in the
sense of its full profile and interface, at any given point in its
life.  The idea was to be able to get some kind of rich response from
the object, well beyond what respond_to? and is_a? provide, in order
to determine whether you'd gotten hold of the type of object you
needed.  I seem to recall it turned out to be very challenging,
perhaps impossible, to come up with a complete system for this.  I'm
not sure if anyone is still working on it.  But it's an interesting
area.

>
> Am I making sense yet, or do I just need to go to sleep now?

Definitely the former, and perhaps the latter too -- 'tis up to you
:-)  I'm also very tired, and feeling semi-coherent at best, but
enjoying the thread.


David
Gary W. (Guest)
on 2007-03-02 05:44
(Received via mailing list)
On Mar 1, 2007, at 9:49 PM, removed_email_address@domain.invalid wrote:
> I guess I tend to think in terms of error handling: that is, let
> objects call [], but catch the ones that fail, or the ones that hand
> back nonsense (in the context) values.

Let me make the situation a little more concrete.

I'd like to define a class that accepts the following syntax for
construction:

  A.new
  A.new(1)
  A.new(1,2)
  A.new(3 => 4)
  A.new(1, 3 => 4)
  A.new(1, 2, 3 => 4)

So the arguments to A.new are zero or more objects followed by an
optional hash. I can certainly look for that trailing hash via
(Hash === args.last) but what if I don't want to lock it down to
a Hash?

  tree = RBTree.new
  A.new(1, 2, tree)

I'd like that to work also and I'm sure there are other sorts of
objects that would work just fine (i.e. respond to #fetch/#[], has_key?,
and perhaps is Enumerable). If I use a class based test to discover
if the last argument is an instance of Hash, I'm eliminating those
other possibilities.  I also don't want to use args.last[key] and
catch an exception because that is only useful *after* I've
discovered if an optional final hash-like object has been passed.

I could have different constructors:

  A.new(1)
  A.new_with_hash(1, 1=>2)

but it really isn't as nice, IMHO.

At first I thought I could use respond_to?(:[]) on the last argument,
but as I said in the original post integers and strings will create
a false-positive for a hash-like trailing argument using that test.

Perhaps I'm trying to push the duck-typing too far and should just stick
with testing for Hash but it seems like testing for #fetch gives at
least
a little more flexibility.

It also seems like it might be nice to encourage a practice of defining
#fetch, #store, and #has_key? for data structures that are 'indexable'.

Gary W.
Ken B. (Guest)
on 2007-03-02 07:10
(Received via mailing list)
On Fri, 02 Mar 2007 10:01:06 +0900, Gary W. wrote:

>    has_key?(key)
>    fetch(key)
>    store(key, val)
>
> In a quick look it seems like only Hash and RBTree implement those
> methods though.

Sounds like you want C++200x concept checking, but that depends very
heavily on static typing.

Basically, I think you want to know (in a non-mutating way) whether #[]
supports various types non-integer parameters. I doubt there's any way
to
do that in Ruby. You could try indexing it and see if it throws a
TypeError (like an Array will), but when you call #[] on Hash.new{|h,v|
h
[v]=0}, #[] is mutating.

--Ken
Joel VanderWerf (Guest)
on 2007-03-02 07:39
(Received via mailing list)
Gary W. wrote:
>
> a Hash?
There seems to be still some ambiguity in this description. In this
case:

   h = {3 => 4}
   A.new(1, 2, h)

how do you know if _h_ is intended as the third object (in the "zero or
more objects" part) or as the optional hash?

Sometimes I have wished that the hash generated by this syntax:

meth(k=>v)

were flagged in some way, so that you could distinguish it from

meth({k=>v})

But I'm not sure that would help in this case anyway.
Gary W. (Guest)
on 2007-03-02 08:16
(Received via mailing list)
On Mar 2, 2007, at 12:39 AM, Joel VanderWerf wrote:
> There seems to be still some ambiguity in this description. In this
> case:
>
>   h = {3 => 4}
>   A.new(1, 2, h)
>
> how do you know if _h_ is intended as the third object (in the
> "zero or more objects" part) or as the optional hash?

You don't.  There just has to be a clear documentation for
the disambiguation rule. The caller could use:

    A.new(1,2, h, {})

If they wanted to force h to be part of the list of objects
instead of the optional trailing hash.


Gary W.
Joel VanderWerf (Guest)
on 2007-03-02 08:36
(Received via mailing list)
Gary W. wrote:
> You don't.  There just has to be a clear documentation for
> the disambiguation rule. The caller could use:
>
>    A.new(1,2, h, {})
>
> If they wanted to force h to be part of the list of objects
> instead of the optional trailing hash.

Another possibility, unless you need to use the block that's passed to
A.new for something else:


class A
   def initialize(*args)
     @args = args
     @opts = block_given? ? yield : {}
     puts "args=#{@args.inspect} opts=#{@opts.inspect}"
   end
end

A.new(1, 2, 3)                # args=[1, 2, 3] opts={}
A.new(1, 2, 3) {{4=>5, 6=>7}} # args=[1, 2, 3] opts={6=>7, 4=>5}
A.new(1, 2, {3=>4})           # args=[1, 2, {3=>4}] opts={}
A.new(1, 2, 3=>4)             # args=[1, 2, {3=>4}] opts={}


But that's syntactically less tidy.
Leslie V. (Guest)
on 2007-03-02 14:55
(Received via mailing list)
On 3/2/07, Gary W. <removed_email_address@domain.invalid> wrote:
>
> a Hash?
> discovered if an optional final hash-like object has been passed.
>
> I could have different constructors:
>
>         A.new(1)
>         A.new_with_hash(1, 1=>2)
>
> but it really isn't as nice, IMHO.
>
> At first I thought I could use respond_to?(:[]) on the last argument,
> but as I said in the original post integers and strings will create

Is it *really* a problem that strings and integers produce values that
your method would make use of? Say someone wants to encode those input
parameters into a string - as long as [] works, they can. Why is this
a problem?
Gary W. (Guest)
on 2007-03-02 15:59
(Received via mailing list)
On Mar 2, 2007, at 7:54 AM, Leslie V. wrote:
> Is it *really* a problem that strings and integers produce values that
> your method would make use of? Say someone wants to encode those input
> parameters into a string - as long as [] works, they can. Why is this
> a problem?

As a general response, I'd say that there is a strong semantic
difference between objects that store key/value pairs (Hash,
OrderedHash,
RBTree, etc.) and objects that aren't general collections but do
have some sort of indexing features (Integers, Strings).  Arrays are
sort of in the middle.

You certainly can consider integers as ordered collections of bits
and strings
as collections of bytes (and sometimes that would be just great) but at
other times I think it is reasonable to consider integers
and strings as distinctly different than a hash or a tree.

I suppose this is a general problem of agreeing on some sort of
taxonomy for
data structures and being able to dynamically query for various
features.
Smalltalk pushes this a lot farther than Ruby: Dictionary,
IdentityDictionary,
Bag, Set, SortedCollection, ArrayedCollection, ByteArray, String,
Text, Array,
RunArray, LinkedList, OrderedCollection.

Usually I would avoid introspection on an object but it seems like
when you
are designing DSLs and/or dealing with object construction it is
sometimes
necessary to be a little more nosey.

I'm going to stick with testing for #fetch when I want to know if an
object
is a collection with keys.  I think this is marginally better than using
(Hash === obj).  I can always test for #at if I want to pick out Arrays.

Gary W.
Leslie V. (Guest)
on 2007-03-02 17:26
(Received via mailing list)
On 3/2/07, Gary W. <removed_email_address@domain.invalid> wrote:
> RBTree, etc.) and objects that aren't general collections but do
> have some sort of indexing features (Integers, Strings).  Arrays are
> sort of in the middle.

But why does the difference matter? Are you going to be using the
parameters to your function in a way that would make Arrays or Strings
throw exceptions? Then you would need to create some sort of test for
that usage.

Otherwise, why restrict the user of your method this way?
Gary W. (Guest)
on 2007-03-02 18:05
(Received via mailing list)
On Mar 2, 2007, at 10:25 AM, Leslie V. wrote:
> Otherwise, why restrict the user of your method this way?

The entire context is that of object construction and how best to
'parse' an argument list.  I'm trying to focus on this very
specific context (which is closely associated with DSL designs)
and not the instance method situation where you've already got
a constructed object.

I'll go back to my simple example:

  Array.new 3
  Array.new [1,2]

Do you really think that Array.new(3) should result in
[1, 1], by viewing the number 3 as an array of bits?  Or
alternatively should Array.new([1,2]) fail because it
doesn't make sense to allocate [1,2] entries?

Array.new must 'parse' its argument list and act
accordingly. The interface to Array could be:

  Array.new( 3 )
  Array.new_from_arrayish_obj( [1,2] )

so as to avoid the overloading problem but I don't
particularly think this is an improvement.  Array#new
already uses a form of duck typing:

h = {1,2,3,4}
def h.to_ary; to_a; end

a = Array.new h
puts a      # [[1,2],[3,4]]

Array#new doesn't even check to see if something is an instance
of Array but instead tries #to_ary.  If it fails then Array trys
#to_int, and if that doesn't work reports an exception.

My original question was how best to do this with hash-like objects.
David Black suggested #to_hash, which makes a lot of sense but it
might result in slurping up a huge data structure into an in-memory
hash when all you really want to do is look things up by keys.

Based on this thread discussion, it doesn't seem there is an
'obvious' answer for all (or most) situations.
There are a variety of tradeoffs between using #to_hash, trying #[] or
#fetch, testing for #has_key?, or even simply iterating with #each.  All
of those seem marginally better than using #kind_of?(Hash).

Gary W.
Rick D. (Guest)
on 2007-03-02 20:48
(Received via mailing list)
On 3/1/07, removed_email_address@domain.invalid 
<removed_email_address@domain.invalid> wrote:
>  A few years ago there were some interesting attempts to
> come up with a systematic way to determine an object's type, in the
> sense of its full profile and interface, at any given point in its
> life.  The idea was to be able to get some kind of rich response from
> the object, well beyond what respond_to? and is_a? provide, in order
> to determine whether you'd gotten hold of the type of object you
> needed.  I seem to recall it turned out to be very challenging,
> perhaps impossible, to come up with a complete system for this.  I'm
> not sure if anyone is still working on it.  But it's an interesting
> area.

All of this is personal perspective of course, but my view of duck
typing is that it's really a question of the type of a variable rather
than on objects.  In other words in my view variables have types which
are generated by their usage.  Types in this view are like job
requirements.

So rather than talking about say an array type, to me duck typing is
talking about the type of a variable which is used in a certain way,
which might be somewhat idiosyncratic to the user. Some common such
types do exist, like a queue, a stack, a generalized collection (with
various requirements as to access, ordering etc.)  given one of these,
or a more idiosyncratic type, several objects might work as the value
of the variable in question.

As an example, lets say I'm looking for something to use to drive a
nail.  The obvious 'type' of thing for this job is a hammer, but a
heavy wrench, or a rock can also serve since the usage really just
requires a mass which can be conveniently accelerated so as to impart
inertia to that nail.  If I don't have a hammer to hand, I can press
one of these other objects into service.

In this view of duck typing choosing an object is akin to hiring
someone, you make an initial assessment of whether the potential
employee has the requirements, and if s/he passes that sniff test, you
hire him/her and test that assessment over time.

These kind of types also can require much more than a simple list of
provided interfaces, they also typically rely to one degree or another
on the semantics of those interfaces, often including how the object's
observed behavior is affected by the SEQUENCE of calls.  These types
of types are much harder to statically check. And they cause bugs in
either a statically or dynamically typed system.  In my experience,
the kind of stupid bugs which are flushed out by a static type system
are a small percentage of the bugs which are caused by these semantic
mismatches.

I know that this viewpoint cause conniption fits in folks who believe
in the religion of static type checking, and I've long ago given up
trying to proselytize those with strong convictions.  All I can say is
that I've found such a view of typing combined with a language such as
Ruby which supports it has provided a powerful approach to building
software.

Both static and dynamic typing have benefits and problems, speaking
solely for myself, I just prefer both the benefits and the problems of
dynamically typed systems over those of statically typed ones.

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/
unknown (Guest)
on 2007-03-02 21:32
(Received via mailing list)
Hi --

On Sat, 3 Mar 2007, Rick DeNatale wrote:

>> area.
>
> All of this is personal perspective of course, but my view of duck
> typing is that it's really a question of the type of a variable rather
> than on objects.  In other words in my view variables have types which
> are generated by their usage.  Types in this view are like job
> requirements.

I'm not sure how that detaches it from the object, though, since
variables are going to contain references to objects and messages are
sent to objects rather than variables.  Do you mean in terms of
documentation?

> So rather than talking about say an array type, to me duck typing is
> talking about the type of a variable which is used in a certain way,
> which might be somewhat idiosyncratic to the user. Some common such
> types do exist, like a queue, a stack, a generalized collection (with
> various requirements as to access, ordering etc.)  given one of these,
> or a more idiosyncratic type, several objects might work as the value
> of the variable in question.

I don't think duck typing has ever been about a checklist of common
data structures or types (queue, stack, etc.), but more about just
sending messages to objects without a lot of pre-message checking as
to their class and ancestry and so forth.  To me, the ultimate duck
typing idiom is:

   def m(x)
     x << "string"
   end

There's no checking, no probing the object -- you just ask it to do
something, and deal with what happens.  In the end, that's *always*
how things are when writing Ruby.  I've always seen "duck typing" as
just a kind of embrace of the conditions under which every single line
of Ruby is code is written anyway.

> As an example, lets say I'm looking for something to use to drive a
> nail.  The obvious 'type' of thing for this job is a hammer, but a
> heavy wrench, or a rock can also serve since the usage really just
> requires a mass which can be conveniently accelerated so as to impart
> inertia to that nail.  If I don't have a hammer to hand, I can press
> one of these other objects into service.

All these nouns sound more like classes than types.  Actually I don't
think the type of a Ruby object can ever be named.  It's circular: the
type of an object is... the type of objects that are of this object's
type :-)  As soon as it gets noun-like it starts getting class-bound
again.

A duck-typing approach to hammering would be something like:

   def hammer(h,n)
     h.pound(n)
   end

Everything's so in the moment.  I know I have a somewhat Utopian view
of Ruby types sometimes :-)  But I really do find the implications
rather enthralling.

Noun-like-ness, by the way, has always seemed to me to be the
Achilles' heel of the duck-typing analogy -- specifically, the part
where it says: "... then it *is* a duck."  That has a way of leading
people back, quite literally, to is_a? -- as if the idea was that Duck
was a class.  (I know that's not what you're saying; I'm just reminded
of this thought.)  I've seen it rewritten (I can't remember whether it
was by me or Florian G.; one of us, I think) as:

   If it walks like a duck and quacks like a duck, then it walks like
   a duck and quacks like a duck.

:-)

> In this view of duck typing choosing an object is akin to hiring
> someone, you make an initial assessment of whether the potential
> employee has the requirements, and if s/he passes that sniff test, you
> hire him/her and test that assessment over time.

I think it's that very initial assessment that Gary was talking about:
is there something -- not static typing, but some kind of dynamic,
run-time mechanism -- that can provide pre-method-call semantics of an
object?

> These kind of types also can require much more than a simple list of
> provided interfaces, they also typically rely to one degree or another
> on the semantics of those interfaces, often including how the object's
> observed behavior is affected by the SEQUENCE of calls.  These types
> of types are much harder to statically check. And they cause bugs in
> either a statically or dynamically typed system.  In my experience,
> the kind of stupid bugs which are flushed out by a static type system
> are a small percentage of the bugs which are caused by these semantic
> mismatches.

The question, though, is whether it's possible to come up with a
semantically rich way of reflecting on the type of a Ruby object.
It's not a static-vs.-dynamic thing; static checking wouldn't enter
into it.  It's more a question of having something that would do what
people think class-checking does before they realize that it doesn't,
if you see what I mean :-)  It may well not be possible.


David
Gary W. (Guest)
on 2007-03-02 22:11
(Received via mailing list)
On Mar 2, 2007, at 2:31 PM, removed_email_address@domain.invalid wrote:
> The question, though, is whether it's possible to come up with a
> semantically rich way of reflecting on the type of a Ruby object.
> It's not a static-vs.-dynamic thing; static checking wouldn't enter
> into it.  It's more a question of having something that would do what
> people think class-checking does before they realize that it doesn't,
> if you see what I mean :-)  It may well not be possible.

I think that a consistent and well-known naming protocol gets you
pretty far down the road.  David, consider your example:

   def m(x)
     x << "string"
   end

The only reason that this is useful at all is that there are some
generally agreed upon semantics for methods named '<<'.  It may be
that for some objects Mystery#append does the same thing but that
isn't as useful within the Ruby ecosystem as the exact same method
named '<<' instead.

The well-known but not-actually-enforced rules for '<<' aren't universal
of course.  Sometimes '<<' means 'shift' rather than 'append'.  But
that alternate view is useful within its own subset of the Ruby
ecosystem.  The fact that the two views are mutually exclusive is OK
because a human is generally making sure that in any particular context
only one view or the other is being assumed.

I guess my point, which certainly isn't original, is that a consistent
naming methodology can often be a substitute or proxy for semantics.
Instead of declaring semantics via some sort of machine readable formal
language we cheat and intuit semantics from the patterns of syntax
created by the naming methodology.

Gary W.
Rick D. (Guest)
on 2007-03-03 00:27
(Received via mailing list)
On 3/2/07, removed_email_address@domain.invalid 
<removed_email_address@domain.invalid> wrote:
> Hi --
>
> On Sat, 3 Mar 2007, Rick DeNatale wrote:
>

> > All of this is personal perspective of course, but my view of duck
> > typing is that it's really a question of the type of a variable rather
> > than on objects.  In other words in my view variables have types which
> > are generated by their usage.  Types in this view are like job
> > requirements.
>
> I'm not sure how that detaches it from the object, though, since
> variables are going to contain references to objects and messages are
> sent to objects rather than variables.  Do you mean in terms of
> documentation?

First of all let me reiterate that this is my personal perspective,
based on 20+ years of using a variety of dynamic OO languages.

I'm trying to say that the code which uses objects in general sees
them through variables.  This isn't always evident, and it's muddied
because so often the same code which uses a variable initializes it,
but in the case of parameters, or the results of expressions based on
parameters that code has less control over what the objects referenced
by variables really are.

One way to think about what I'm saying is to think of the programmer
like a playwright who writes a script in terms of dramatis personae or
roles, without necessarily knowing or caring which actors will
ultimately play those roles.

Many years ago I wrote a paper called "Types from the Client's
Viewpoint" which talked about this, A PDF of this is available on my
blog website at
http://talklikeaduck.denhaven2.com/files/TypesFrom...

I've been struggling for many years about how to describe this without
getting wrapped up in what is really a paradigm shift which confounds
discussion since terms like type have subtly different meanings.  That
paper is an early attempt, but it provides on such attempt.  Another
glimmer of what I'm trying to say and it's evolution can be seen in my
mini-memoir
http://talklikeaduck.denhaven2.com/articles/2006/0...

Because of this paradigm shift problem, I sometimes use the term role
instead of type when talking about variables in an analogy the
playwright metaphor.

One of the key aspects of the paradigm shift is that in a dynamically
typed system a strong wall of encapsulation can be erected between the
user of an object and the implementation of that object.
Traditionally types in programming languages are really names or
calculi for describing how a string of bits are to be interpreted by a
program using those bits.  In a language like Ruby or Smalltalk, all
the bit interpretation gets done by the object itself, so the user of
the object doesn't or shouldn't care about the implementation of the
object.  This is why those of us in the dynamic duck typing camp are
so adverse to class checking, since the class of an object is an
implementation matter.

This encapsulation wall at the interface between object and user is
what I consider the hallmark of  object centered softwre design  This
is what Alan Kay seemed to have in mind when he coined the term
"Object Oriented Programming."  Unfortunately, Peter Wegner came along
later and coopted the term to require classes and inheritance, both of
which might be useful implementation techniques but are non-essential
to my mind.

And the fact that classes and inheritance are implementation
techniques is another thing which makes those familiar with C++ like
languages have difficulties in getting duck typing. Since
traditionally types are a way of describing an implementation rather
than more abstract usage requirements, classes, inheritance, and the
concept of a strong type all get wrapped so tightly together that they
can't separate them.

> sending messages to objects without a lot of pre-message checking as
> to their class and ancestry and so forth.

I wasn't saying that all of the roles fall into one of these
checklists, but that these and their variations of them are common
roles, just like there are common roles like ingenue, jilted lover,
thug etc.  Other roles are more inventive and complex, like the
ex-astronaut rancher building a missile in his barn, or an autistic
savant called rain man.

> just a kind of embrace of the conditions under which every single line
> of Ruby is code is written anyway.

Yes, and this is completely compatible with what I'm saying.  Think
again about the play, the playwright writes the play, actors are cast,
and the production of the play is developed through rehearsal
(testing), debugging (re-writes) and sometimes re-casting.  Later
productions can use different casts, possibly with additional
adaptation.

> type :-)  As soon as it gets noun-like it starts getting class-bound
> again.

Which nouns?  hammer, wrench, rock, yes you can think of these as
classes (but more on this in a bit).  But mass?

Now you are right that it's often hard to name a role, but never say
never.  The name might stand for a complex type like Romeo, Othello,
or Tony Soprano, or it might be a noun phrase like "mass which can be
conveniently accelerated...".

The point though is that naming it isn't enough.  Again in an analogy
with writing a play, the playwright's conception of the role doesn't
usually spring full-blown when the character's name gets written down,
but evolves as the play is written and the character interacts with
other characters in the play, and then further evolves as the director
begins to interpret the play.

Imagine Shakespeare writing Romeo and Juliet in C++, he'd have to
pre-declare all the players, and come up with strong types closely
bound to the implementation of the actors, before he wrote a line of
dialog.

Ay there's the rub!



--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/
Dean W. (Guest)
on 2007-03-03 05:47
(Received via mailing list)
...
>
>    If it walks like a duck and quacks like a duck, then it walks like
>    a duck and quacks like a duck.
>
> :-)

"... behaves like a duck", rather than "... is a duck", perhaps?
unknown (Guest)
on 2007-03-03 06:21
(Received via mailing list)
Hi --

On Sat, 3 Mar 2007, Rick DeNatale wrote:

> Now you are right that it's often hard to name a role, but never say
> never.  The name might stand for a complex type like Romeo, Othello,
> or Tony Soprano, or it might be a noun phrase like "mass which can be
> conveniently accelerated...".

It's not that there aren't things-that-can-be-named, but that I don't
use the word "type" to refer to any of those things in Ruby.  You can
say things like "This object is Enumerable", or "This object
implements what lots of people think is a hash-like interface".  But
those aren't the object's type, though they may tell you part of the
picture.

In fact, I find the word "type" pretty useless in connection with
Ruby.  It's possible to come up with some salient characteristic of
how Ruby objects are engineered, and call that "type", but I've never
found the word really useful.  My characterization of it as circular
(an object is of type "the type of objects of this type") is really
another way of saying that it's not a very useful term.  It doesn't,
to borrow from one of your analogies, hit any of the available nails
on the head :-)

So I think we should really talk about interesting ideas like roles
and behaviors and encapsulation, and forget "type".

> dialog.
I don't think this is what's at stake in any of this discussion,
though.  We're all hip to the dynamicness of Ruby :-)  No one's
talking about static typing.  But I'd be happy to experiment with a
moritorium on the word "type".


David
Rick D. (Guest)
on 2007-03-03 17:32
(Received via mailing list)
On 3/2/07, Dean W. <removed_email_address@domain.invalid> wrote:
> ...
> >
> >    If it walks like a duck and quacks like a duck, then it walks like
> >    a duck and quacks like a duck.
> >
> > :-)
>
> "... behaves like a duck", rather than "... is a duck", perhaps?

Can serve as a duck.

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/
Robert D. (Guest)
on 2007-03-03 17:37
(Received via mailing list)
On 3/3/07, Rick DeNatale <removed_email_address@domain.invalid> wrote:
> Can serve as a duck.
>
Can be served as a duck!

But I read something more into Dean's joke, maybe duck typing shall
not be confused with introspection too much.

I will make it walk and talk and eat it as if it were a duck and let
us not worry about digestion right now.

Robert
Rick D. (Guest)
on 2007-03-03 17:37
(Received via mailing list)
On 3/2/07, removed_email_address@domain.invalid 
<removed_email_address@domain.invalid> wrote:
> >
> > Imagine Shakespeare writing Romeo and Juliet in C++, he'd have to
> > pre-declare all the players, and come up with strong types closely
> > bound to the implementation of the actors, before he wrote a line of
> > dialog.
>
> I don't think this is what's at stake in any of this discussion,
> though.  We're all hip to the dynamicness of Ruby :-)  No one's
> talking about static typing.  But I'd be happy to experiment with a
> moritorium on the word "type".

Well many of us are in fact hip to it, but the fact that attempts to
formalize or mechanize 'duck typing' indicate that not all who swim in
the waters of this group are. And since ruby-talk gets reflected to
comp.lang.ruby and therefore many of the postings get cross-posted to
various comp.lang.* groups means that there are at least some static
typing advocates who read some of what transpires here.

As for avoiding the term type to describe objects are used in a
dynamic language, I'm all for it.

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/

IPMS/USA Region 12 Coordinator
http://ipmsr12.denhaven2.com/

Visit the Project Mercury Wiki Site
http://www.mercuryspacecraft.com/
Ed Howland (Guest)
on 2007-03-03 20:03
(Received via mailing list)
On 3/2/07, Gary W. <removed_email_address@domain.invalid> wrote:
> I'll go back to my simple example:
> accordingly. The interface to Array could be:
>
>         Array.new( 3 )
>         Array.new_from_arrayish_obj( [1,2] )
>
> so as to avoid the overloading problem but I don't
> particularly think this is an improvement.  Array#new
> already uses a form of duck typing:
  [...]
> Based on this thread discussion, it doesn't seem there is an
> 'obvious' answer for all (or most) situations.
> There are a variety of tradeoffs between using #to_hash, trying #[] or
> #fetch, testing for #has_key?, or even simply iterating with #each.  All
> of those seem marginally better than using #kind_of?(Hash).
>
> Gary W.

This may be OT, (or merely off-base,) but somewhere else on this list
I read about using mutators (not sure if that is the formal name or
not) on #new. This keeps the complexity out of initialize having to
parse each argument for its type.

E.g.

   my_obj = MyClass.new    # no arg ctor
   my_obj = MyClass.new('John', 'Smith') # canon - takes strings
   my_obj = MyClass.new.from_hash {:last_name => 'Smith', :first_name =>
'John'}
   my_obj = MyClass.new.from_arr ['John', 'Smith']

class My; def from_hash(hash); @first = hash[:first]; @last =
hash[:last]; self; end; end

Anyway, I just think this is a reasonable way to extend the
initializer for other argument types. I'd use this when the % of times
I need to construct an object from another type is less than say 33
(JAROT).

Ed
Robert D. (Guest)
on 2007-03-03 21:23
(Received via mailing list)
On 3/3/07, Ed Howland <removed_email_address@domain.invalid> wrote:
> >
> > Array.new must 'parse' its argument list and act
> > 'obvious' answer for all (or most) situations.
>
> E.g.
>
>    my_obj = MyClass.new    # no arg ctor
>    my_obj = MyClass.new('John', 'Smith') # canon - takes strings
>    my_obj = MyClass.new.from_hash {:last_name => 'Smith', :first_name => 'John'}
>    my_obj = MyClass.new.from_arr ['John', 'Smith']

No that is not OT at all, one of the strength of ruby IMHO
look e.g. at REXML::Document.new and many others.

Those interface generalizers shall be used on well defined entry
points only, I feel.

Cheers
Robert
unknown (Guest)
on 2007-03-03 21:42
(Received via mailing list)
Hi --

On Sun, 4 Mar 2007, Rick DeNatale wrote:

>> moritorium on the word "type".
>
> Well many of us are in fact hip to it, but the fact that attempts to
> formalize or mechanize 'duck typing' indicate that not all who swim in
> the waters of this group are. And since ruby-talk gets reflected to
> comp.lang.ruby and therefore many of the postings get cross-posted to
> various comp.lang.* groups means that there are at least some static
> typing advocates who read some of what transpires here.

On the other hand, if a thread about types *isn't*, for once, a
"debate" about static "vs." dynamic typing, just enjoy it! :-)


David
Gary W. (Guest)
on 2007-03-04 02:41
(Received via mailing list)
On Mar 3, 2007, at 1:02 PM, Ed Howland wrote:
> 'Smith', :first_name => 'John'}
>   my_obj = MyClass.new.from_arr ['John', 'Smith']

This certainly simplifies MyClass#initialize but Ruby is flexible enough
that you can create your own methods to allocate and initialize
without going
through MyClass#initialize:

class MyClass
   class <<self
     def new_from_hash(h)
       o = allocate
       o.send(:initialize_from_hash, h)
       o
     end
   end
   attr_accessor :pairs
   def initialize_from_hash(h)
     @pairs = h.to_a
   end
   private :initialize_from_hash
end

mc = MyClass.new_from_hash({1,2,3,4})
puts mc.pairs        # [[1,2],[3,4]]


Gary W.
This topic is locked and can not be replied to.