How to ducktype a Hash?

R

Robert Klemme

Gavin Sinclair said:
I think what we may be running into is the possibility that
"ducktyping a Hash" is in a sense a contradiction in terms.

Let me just say that Sean, despite his protestations, doesn't really
want to check that something is "hash-like". He wants to check that
something is "map-like". Forget fetch, store, [], etc. A map has two
essential properties: "keys" and "values". OK, we can all go home now
:)

Well, you can even view an Array as map like: keys are integers and values
whatever you like.

Kind regards

robert
 
G

Gavin Sinclair

Gavin Sinclair said:
I think what we may be running into is the possibility that
"ducktyping a Hash" is in a sense a contradiction in terms.

Let me just say that Sean, despite his protestations, doesn't really
want to check that something is "hash-like". He wants to check that
something is "map-like". Forget fetch, store, [], etc. A map has two
essential properties: "keys" and "values". OK, we can all go home now
:)
Well, you can even view an Array as map like: keys are integers and values
whatever you like.

Yeah, but that's a crappy kind of map :) That's more like an array :\

Gavin
 
R

Robert Klemme

Gavin Sinclair said:
Gavin Sinclair said:
On Monday, June 7, 2004, 11:27:41 PM, David wrote:

I think what we may be running into is the possibility that
"ducktyping a Hash" is in a sense a contradiction in terms.

Let me just say that Sean, despite his protestations, doesn't really
want to check that something is "hash-like". He wants to check that
something is "map-like". Forget fetch, store, [], etc. A map has two
essential properties: "keys" and "values". OK, we can all go home now
:)
Well, you can even view an Array as map like: keys are integers and values
whatever you like.

Yeah, but that's a crappy kind of map :)

It depends on how much abstraction you throw in. :) RAM can be viewed as
this kind of mapping, too...
That's more like an array :\

How did you know? You didn't to #kind_of? Array, did you? :)

robert
 
G

Gavin Sinclair

It depends on how much abstraction you throw in. :) RAM can be viewed as
this kind of mapping, too...
How did you know? You didn't to #kind_of? Array, did you? :)
a = [1,2,3,4] => [1, 2, 3, 4]
def a.kind_of?(klass) true end => nil
a.kind_of? Hash
=> true

Hey, I think you're onto something.
=> true

Now that's just being silly.

Gavin
 
S

Sean O'Dell

Sean O'Dell wrote:

It would be interesting to compare our techniques for while I have a
similar background (e.g. over 25 years of programming experience and
low-defect rates), in recent years I have found myself becoming a strong
advocate of test-first/test-driven development. TFD/TDD has not lowered
my defect rate, nor has it found more bugs for me. What it does do is
allow me to refactor the code with more speed and confidence.

It's hard to describe my technique. I just have a lot of little habits that
add up to a lot of smooth sailing. I guess it would amount to a lot of petty
things, as opposed to XP's tiny set of strange and radical ideas. My
techniques are boring, but practical. I think if I could sum up a lot of
them, it would sound something like this: "I set up landmines for myself so
that anything that needs to be done explodes in my face the minute I get
there, so I remember that it needs attention." It sounds silly put that way,
but the net effect is I can pay attention to a huge number of things by
forgetting what I don't need to worry about right now, and after a certain
point, when I've ground through it all, every last issue ended up getting a
lot of quality attention.

Which is why some form of typing is so important to me. Being able to test
for what I need easily, without having to craft out custom solutions every
time this comes up, to me, is absolutely necessary. Without it, it's a
time-sink to deal with, and I find that there are certain types of errors
that are cropping up more often than I am accustomed to, and a form of type
checking would take care of it easily.
[...] and a lot of younger programmers
haven't figured it all out yet and unit tests get them to a higher
quality of code quicker.

This is the comment that triggered my response. Its not just the young
programmers who are finding TDD effective. A lot of us old-timers are
too.

I wish I knew how. I've re-visited the issue a number of times, and it's
always left me unproductive and paying more attention to the tests than I
felt I should. I also found that organizing them to be a great hassle.
Sometimes I need to deliberately break parts of my code to work on others
more easily, and when unit tests start throwing errors in my face about the
disabled code, I find I have to stop thinking about my programming and go
spend more time arranging my unit tests to shut them down.

I also find that my projects are done before I ever really get my unit tests
organized. I mean it. I have a small army of components/projects here that
all work fantastic, but for which I probably need to spend time to get the
unit tests working completely. This is what always happens. I see the light
at the end of all my tunnels, and I can't justify delaying finishing things
just to get the unit tests finished. So the unit tests never end up helping
me.

Sean O'Dell
 
S

Sean O'Dell

You can usually get rid of those checks by using polymorphism. In Ruby
it's
possible to get rid of them without using inheritace but using duck
typing
instead. Consider this refactoring:

You can't morph everything. What if you expect an entire tree of data loaded
up from REXML, but instead got an open socket object? You think you can
polymorph an open socket object to behave like a REXML object.

Type checking is necessary in many, many instances. It is not an indication
of weak OO design.

Sean O'Dell
 
S

Sean O'Dell

In both cases, does that mean that the object has hash-like
functionality, or just that the object responds to those two methods? Is
there some definition somewhere that says "it's a hash if it has these
methods?" I actually have hash-like objects that DON'T respond to either
of those two methods, but it would be easy enough to add fake ones. I
hate adding fake methods just to id a hash interface, it's a kludge.

Sean O'Dell

Guess, you will be happy if Ruby can do some like ...

obj.respond_to?(Hash::[])
or
obj.respond_to?(Foo::[])

Yes, if obj.respond_to?(Hash::[]) can be called for non-Hash objects, and for
objects that don't derive from Hash, but which implement hash-like
functionality. The semantics aren't important. That method of interface
checking would work as well as any.

Sean O'Dell
 
S

Sean O'Dell

param.respond_to? '[]' and
param.respond_to? '[]=' and
!param.kind_of? String and
!param.kind_of? Array

Other objects also respond to [] and []= which are not Strings or Arrays. The
number of them are potentially infinite, so this is not what I consider a
good solution.

Sean O'Dell
 
S

Sean O'Dell

Richard said:
2) Sean: It seems like what you are looking for is to check a method for
'semantic equivalence'...that is, you want to say that the [] and []=
methods on an object are semantically equivalent to the [] and []=
methods on Hash. Not that the object is in any way (from and inheritance
perspective) related to a Hash, nor does it actually get the behavior of
a Hash object's [] and []= methods, but when it comes to those two
methods...'quack like a Hash'. Does that sound right? If so, the
question becomes how to express this kind of semantic equivalence both in
the definition of the methods, and the checking with respond_to?

I've been watching this thread and I think you've hit the nail on the
head. Sean seems to want semantic equivalence, but is using a syntax
which is overloaded out of the box. Duck typing is based on syntax
matching. It seems to me that the only way to get semantic equivalence
without using type cues or explicit testing is to steer clear of
overloaded operators and methods, so that syntax reflects semantics.
This is what several others were suggesting when they talked about using
hash's alternate method names in place [] and []=.

I'm not sure what this means. I'm not looking for duck typing per se, just a
way to type check hash-like objects. The only two things I care about when
checking for something in this way are that the object responds to a group of
methods which all take the same number of parameter, and the group of methods
represent a described interface; that is, a known functionality. In this
particular case, the functionality should be that of a hash, or a hash-like
object.

Sean O'Dell
 
S

Sean O'Dell

I think what we may be running into is the possibility that
"ducktyping a Hash" is in a sense a contradiction in terms.

Let me just say that Sean, despite his protestations, doesn't really
want to check that something is "hash-like". He wants to check that
something is "map-like". Forget fetch, store, [], etc. A map has two
essential properties: "keys" and "values". OK, we can all go home now

Whatever you need to call it, I'm up for it. =)

Sean O'Dell
 
J

Jim Weirich

I think the problem Sean is dealing with is one of "type discovery" rather
than "type checking". In other words, Sean is getting a stream of objects
comming from some outside source (my impression was that it was
reconstituted objects from a YAML stream, or something like that).

If the object happens to be a container-like object (such as a Hash or an
object with hash-like qualities), then Sean wishes to do something
specific with those objects.

The problem is with the definition of "hash-like". Ruby provides a
perfectly fine way of identifying Hashes (e.g. kind_of?(Hash)). It
doesn't provide a way of identifying things that are almost (but not
quite) hashes.

For example, Sean depends on [] and []= (with certain semantics). Someone
else noted that they check for each_key. And yet another person
recommended checking fetch and store. If an object implements [] and []=,
but not each_key, is it hash-like? If it implements fetch and store, but
not [], is it hash-like?

I think that currently, the concept of hash-like is too vague to be
addressed by the language. Sean's method of tagged classes that fit his
description is a reasonable compromise in this situation.

Here's another idea ... If Sean want to "do_something" with hash like
modules, then mixin the following module to any class you consider
hash-like ...

module HashWork
def do_something
# Code to do something with hash-like objects
end
end

And add the following ...

module Kernel
def do_something
# DO NOTHING!
end
end

Then, just invoke "do_something" on all your objects. The ones that are
hash-like will know what to do...

obj.do_something.

Now this solution adds a method to existing objects, but I think the main
problem with that is just a name clash issue. Make the name of
do_something sufficiently unique and you should have no problems.
 
K

Kristof Bastiaensen

My complaint is that it's not already in the stdlib. I don't expect
everything I want to come from the standard libraries. Most of the code I
call is my own. But when it comes to something like type checking, I really
feel the language should offer SOMETHING sufficient to solve a problem like
this.

Sean O'Dell

Hi, could I ask what is not sufficient about is_a? ?

Kristof Bastiaensen
 
R

Robert Klemme

First of all I'd like to make clear that this is still not Duck Typing!
Duck Typing simply assumes the instance at hand is the one expected. If
it's not the code may run anyway or you'll be bitten by a NoMethodError.

I'm not sure what this means. I'm not looking for duck typing per se, just a
way to type check hash-like objects. The only two things I care about when
checking for something in this way are that the object responds to a group of
methods which all take the same number of parameter, and the group of methods
represent a described interface; that is, a known functionality. In this
particular case, the functionality should be that of a hash, or a hash-like
object.

You can do it like this, although I still dislike the strong focus on type
and type like checks:

#!/usr/bin/ruby

class MethodCriterium
def initialize
@methods = {}
end

def clear; @methods.clear; end

def add(sym, arity)
@methods[sym.to_sym] = arity
self
end

def add_from_template(obj)
obj.methods.each do |meth|
add(meth, obj.method(meth).arity)
end

self
end

def satisfies(obj)
begin
@methods.each do |meth, arity|
return false if arity != obj.method(meth).arity
end

true
rescue NameError
return false
end
end

alias :=== :satisfies
end


a_hash = MethodCriterium.new
a_hash.add:)[], 1).add:)[]=, 2)

p a_hash === {}
p a_hash === []

a_hash = MethodCriterium.new.add_from_template( {} )

p a_hash === {}
p a_hash === []

an_array = MethodCriterium.new.add_from_template( [] )

[ [], {}, 5 ].each do |o|
case o
when a_hash
puts "#{o.inspect} is a hash!"
when an_array
puts "#{o.inspect} is an array!"
else
puts "dunno bout #{o.inspect}"
end
end



Regards

robert
 
G

gabriele renzi

il Mon, 7 Jun 2004 01:53:41 +0900, James Britt
This is an important point. Earlier in this thread there was a comment
along the lines of, "If Ruby was built for duck typing, why weren't the
standard libs better designed to manage the quirky cases?" It struck me
as an odd, and misguided, question, because duck typing is more of an
epiphenomenon. It's not a target of the language design, it's just one
of the things you get as a result.

I wonder if that was my message.
Please let me explain my opinion[1].

Duck Type happens.

I completely agree. But what I see is that the basic building blocks
of it (let's call it basic building quacks, BBQ sounds fine :)
are: some interfaces are common, we can rely on them.
That is true for, say, #read() , for #each(), for #[Object]=o and so
on.


I suggested having a mix-in for , say, Input-like objects.
This is useful, imo, for two reasons:
it let's you build Input-like objects programmatically
it let's you strongly specify they're interface[2].

Why there are *so much* enumerables out there and *so few*
IO objects ? Because a mix-in let's you easily create an enumerable
class, a class that is easy to use (working with a given interface)
and affordable (we can expect that #each will work as expected, and to
have #find_all and so on)
OTOH, we can't expect from an object that has #[]= to behave like an
hash, neither via #[]= neither via #keys or something else.

Having more mix-in is not saying 'ruby should have been designed for
it'.
It is just: now we have lots of objects that may take advantage of ,
say, a Map mix-in (PStore, dbm, rbtree and so on), could we refactor
the stdlib to allow them to use one single interface avoiding little
problems like a missing method or a wrong number of parameters?

Just as a side note, this solves the problem of interface checking.
It solves the problem where it's more needed instead of a general
solution[3]: the place where you want to check an interface you're
probably using a common anonymous interface anyway, and in the other
places you could, if you want, use the "kludgy trick" of adding a
useless tag-module.

If you're thinking that in your own library you make extensive use of
a given interface without declaring it (say, PDF::Writer with
#hyphenate() ) you could:
declare it (include Hyphenable)
ignore the problem, when needed someone could add the tag anyway


just my 2 cent, and I need some cappheine.


[1]
yes, like many other people I want to use matz to implement my
language. It is our right as user to do so, and it is his right as
the project leader to just forget us :) When I'll write my own
language...
[2]
strongly as in 'easy to check'. Lies are ok, I don't think that any of
the proponents of any kind of type checking in ruby wants full
checking, *just something* is ok.
[3]
and everybody will stand up 'we wnt general solutions!". Not all we
do. A simple solution for the most part of a problem may be better
than a convoluted solution for everything.
 
R

Rene van Bevern

Sean said:
So, how do I positively identify when an object is a hash or at least behaves
like a hash?

Use the method #to_hash instead. All hash-like objects should be able to
give a hash-representation of themselves.

Rene
 
S

Sean O'Dell

Use the method #to_hash instead. All hash-like objects should be able to
give a hash-representation of themselves.

Not necessarily. A database abstraction might act like a hash, but wouldn't
dump entire tables, which could contain tens of thousands of records, as one
big Ruby hash. That isn't positive identification.

Sean O'Dell
 
R

Rene van Bevern (RvB)

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Not necessarily. A database abstraction might act like a hash, but wouldn't
dump entire tables, which could contain tens of thousands of records, as one
big Ruby hash. That isn't positive identification.

Okay, you are right. PStore is the first example that comes to my mind:
It can act like a hash but doesn't have #to_hash. :/

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.4 (GNU/Linux)

iD8DBQFAy0A9wm0wNHxxTHgRAvJ4AJsGzLxBbuUcVHWdFFyh6opogvVecgCeNv3E
9wUYM2W8vRgKn2ilT/osoYE=
=eXEy
-----END PGP SIGNATURE-----
 
S

Sean O'Dell

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1


Okay, you are right. PStore is the first example that comes to my mind:
It can act like a hash but doesn't have #to_hash. :/

It's about time someone finally admitted I'm right. I'm going to go tell my
wife again that I'm right, and now I have proof.

Sean O'Dell
 
G

Gavin Sinclair

Not necessarily. A database abstraction might act like a hash, but wouldn't
dump entire tables, which could contain tens of thousands of records, as one
big Ruby hash. That isn't positive identification.

Such an abstraction could give a lazy-loading Hash, as in

class DBAbstraction
def to_hash
Hash.new { |hash, key|
hash[key] = expensive_lookup(key)
}
end
end

So when you call #to_hash in the first place, it's empty. Each key
you call results in a calculation. All unbeknownst to the user of the
hash.

Gavin
 

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

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,057
Latest member
KetoBeezACVGummies

Latest Threads

Top