Hash::MixIn and Python style Object#dict

F

Florian Gross

Moin.

I just did a quick implementation of Hash::MixIn (you provide .keys,
..fetch, .store and .delete and get a full-blown Hash interface) and
Python style Hash access to an Object's methods.

Here's a quick sample IRB session:

irb(main):001:0> obj = "foo"
=> "foo"
irb(main):002:0> obj.dict[:reverse] = "Let's lie.".dict[:reverse]
=> #<Method: String#reverse>
irb(main):003:0> obj.reverse
=> ".eil s'teL"
irb(main):004:0> obj.dict.values_at:)reverse, :+)
=> [#<Method: "foo".reverse>, #<Method: String#+>]
irb(main):005:0> obj.dict.reject! { |key, value| key.to_s[0] == ?r }
=> #<MethodDict: :send=>#<Method: String(Kernel)#send>, ...>
irb(main):006:0> obj.dict.include?:)rindex)
=> false

The implementation of all this is available at
http://flgr.0x42.net/method-dict.rb

I might include Hash::MixIn as a sample in the ruby-contract library as
it would be a good case for automatically adding to the interface when a
given Contract is met.

Anyway, does any part of this look useful to you?

I've heard quite a few requests for a Hash mix-in, but I'm not sure how
to best make this available to the general public.

Regards,
Florian Gross
 
G

gabriele renzi

Florian Gross ha scritto:
Anyway, does any part of this look useful to you?

I've heard quite a few requests for a Hash mix-in, but I'm not sure how
to best make this available to the general public.

I'm starting to think we could have a RubyBase package on RAA to provide
lots of useful mixins (basically for fat classes i.e. IO::Readable,
IO::Writable, String::MixIn, Array::MixIn, but also for new things).
What do others think?
 
D

David A. Black

Florian Gross ha scritto:


I'm starting to think we could have a RubyBase package on RAA to provide lots
of useful mixins (basically for fat classes i.e. IO::Readable, IO::Writable,
String::MixIn, Array::MixIn, but also for new things).
What do others think?

I wouldn't call it RubyBase -- that suggests that it's part of the
base or core language, rather than extensions.


David
 
G

gabriele renzi

David A. Black ha scritto:
I wouldn't call it RubyBase -- that suggests that it's part of the
base or core language, rather than extensions.

Oh, the name was just the first thing coming into my head, maybe I was
somewhat thinking of the Eiffel Base library which is told to be great
on the side of reusability. Or maybe it was Florian talking about
contracts. Anyway, I hope you agree with the underlying concept :)
 
M

Mathieu Bouchard

Florian Gross ha scritto:
I'm starting to think we could have a RubyBase package on RAA to provide
lots of useful mixins (basically for fat classes i.e. IO::Readable,
IO::Writable, String::MixIn, Array::MixIn, but also for new things).
What do others think?

MetaRuby (http://artengine.ca/matju/MetaRuby/) provides that, with
examples implementing #undo/#redo, BitArrays, InstanceVariablesHash,
MethodsHash, FileAsString, ProcAsArray, ... And those examples are all
very short.

Incidentally I have found that letting the user define #[] and #[]= is a
bad idea because those methods are too complex (I mean those in Array and
String) and instead I make the user implement #put, #put_seq, #get,
#get_seq, for which I give extremely simple definitions.

I also use a mixin-based contract together with a mixin-based
implementation. I used to call it all ArrayMixin, HashMixin, etc., and
later changed the name because people found it was a bad name, and the
current names are worse because I confused two similar words in English
(it's a bad idea to assume my English is excellent just because I use a
lot of vocabulary: it's *not*). If I resume that project the names of the
three containers will change again (but all the rest will stay essentially
the same).

_____________________________________________________________________
Mathieu Bouchard -=- Montréal QC Canada -=- http://artengine.ca/matju
 
F

Florian Gross

Mathieu said:
MetaRuby (http://artengine.ca/matju/MetaRuby/) provides that, with
examples implementing #undo/#redo, BitArrays, InstanceVariablesHash,
MethodsHash, FileAsString, ProcAsArray, ... And those examples are all
very short.

Certainly still an interesting project. Does it work properly on 1.8? Is
documentation available online?
Incidentally I have found that letting the user define #[] and #[]= is a
bad idea because those methods are too complex (I mean those in Array and
String) and instead I make the user implement #put, #put_seq, #get,
#get_seq, for which I give extremely simple definitions.

I agreed here, but found .fetch and .store to be relatively simple. The
only complex thing is that .fetch can take fallbacks in the form of
Objects or blocks as well. I'm not sure how to handle that, but I guess
the situation is not too bad right now.

I'm not sure what the .put_seq and .get_seq methods do though. Do they
somehow replace .keys and .delete?
I also use a mixin-based contract together with a mixin-based
implementation. I used to call it all ArrayMixin, HashMixin, etc., and
later changed the name because people found it was a bad name, and the
current names are worse because I confused two similar words in English
(it's a bad idea to assume my English is excellent just because I use a
lot of vocabulary: it's *not*). If I resume that project the names of the
three containers will change again (but all the rest will stay essentially
the same).

How would the mixin-based contract look?
 
G

gabriele renzi

Mathieu Bouchard ha scritto:
MetaRuby (http://artengine.ca/matju/MetaRuby/) provides that, with
examples implementing #undo/#redo, BitArrays, InstanceVariablesHash,
MethodsHash, FileAsString, ProcAsArray, ... And those examples are all
very short.

yes, I know about metaruby but I supposed that it did not work with ruby
1.8. Anyway IIRC your metaruby has one thing I disliked,
since it does not provide a fine grained functionality splitting, I
mean, there are HollowIO and HollowHash instead of
Input+Output+Seekable+whatever and Map+EnumerableMap+whatever.
I may recall wrong, though.

Incidentally I have found that letting the user define #[] and #[]= is a
bad idea because those methods are too complex (I mean those in Array and
String) and instead I make the user implement #put, #put_seq, #get,
#get_seq, for which I give extremely simple definitions.

Yup, I agree
I also use a mixin-based contract together with a mixin-based
implementation. I used to call it all ArrayMixin, HashMixin, etc., and
later changed the name because people found it was a bad name, and the
current names are worse because I confused two similar words in English
(it's a bad idea to assume my English is excellent just because I use a
lot of vocabulary: it's *not*). If I resume that project the names of the
three containers will change again (but all the rest will stay essentially
the same).

why those were bad name?
FWIW I found names like SomethingP *crazy* the first time I read the
code, but now I think of them as lispy things and I can accept them :)

But I wonder: why both you and florian considered the need to have
contracts for this mixable things?
I used to love this, and being scared from mixins like Enumerable wich
do not have checks, but now I feel this is very non-rubyish.
 
F

Florian Gross

gabriele said:
But I wonder: why both you and florian considered the need to have
contracts for this mixable things?
I used to love this, and being scared from mixins like Enumerable wich
do not have checks, but now I feel this is very non-rubyish.

It's basically for letting your users check at the time they do 'include
Hash::MixIn' (in this case it would be 'fulfills Hash::Contract')
whether they have implemented all the prerequisites correctly. For
Enumerable and so on that makes not too much as the only preconditions
are providing an .each method, but I think that this could in general be
quite powerful.

I've also tried to switch away from Class-based typing entirely and to
instead use unit testing for defining contracts. I'm not sure of the
exact implications, but it was nice to finally implement this idea that
I had for so long. I've not fully explored all the possibilities though
so expect to see changes and additions in the future.
 
M

Mathieu Bouchard

Certainly still an interesting project. Does it work properly on 1.8?
No.

Is documentation available online?

All the doc there is, is in the tarball.
I agreed here, but found .fetch and .store to be relatively simple. The
only complex thing is that .fetch can take fallbacks in the form of
Objects or blocks as well. I'm not sure how to handle that, but I guess
the situation is not too bad right now.
I'm not sure what the .put_seq and .get_seq methods do though. Do they
somehow replace .keys and .delete?

No, in the SimpleHashP contract, it's .each_key and .remove that play
those roles, and then .each is made using .each_key, and .delete is made
using .remove.

The .put_seq and .get_seq methods are declared only in the SimpleArrayP
and SimpleStringP contracts. They implement only the a[i,n] case, whereas
put and .get implement only the a case. Basically the two seq methods
are there for efficiency. The actual full [] and []= have more cases than
that, that get implemented in terms of those two.
How would the mixin-based contract look?

Ideally, it would be a set of around-methods that first check the
precondition, then call super, then check the postcondition; but since
Ruby only supports a small subset of Lisp method-lookup (1), it doesn't
support the aspect-oriented features (AOP) of Lisp. There are cool Ruby
packages like AspectR and DbC that do a good job of coercing Ruby into
doing it, and Ruby tends to be quite docile for that kind of thing, but I
wanted to try a different strategy.

So instead the contract is a non-class module (2) which you have to
include in a subclass of your implementation (and not in the
implementation directly, which would either require AOP features,
method-renaming, or another trick).

For example,

class MyHash; include HollowHash # (3)
def each_key(&b)
# blah blah blah
end
# further blah
end

class MyVerifiedHash < MyHash; include SimpleHashP; end

h1 = MyHash.new # unchecked (faster)
h2 = MyVerifiedHash.new # checked (safer)

That's it.



---
Notes:

(1) Namely, it's got something fairly close to Lisp's
(call-next-method) in the shape of "super", but it doesn't have
multiple-dispatch nor method-combinations and it only supports a subset of
backtracking-inheritance because of the following reason ((2)).

(2) Ruby requires the programmer to state explicitly whether a module is
instantiable or not, and those that are, are called classes, and there are
further restrictions that you know regarding what can inherit from
what. Those restrictions are not present in CommonLisp, but Ruby
programmers seem to prefer something more complicated.

(3) HollowHash is called like that because my English is bad. I actually
had ShallowHash in mind, which is 10 times more meaningful, and rhymes.
Nowadays I'm thinking about names like AbstractHash and HashShell.
Especially, the concept of kernel/shell separation as it appeared in 70's
OSes like Multics and Unix reflects pretty much the nature of the beast:
have a small set of low-level features that are have simple interfaces,
and a wrapper around them so that they become convenient to use. An
important note is that a HashShell (HollowHash) is the thing that stays
the same, while the kernel gets specialised for a given task/behaviour. In
laymen's terms, it's like you always interact with your system in the same
way using Bash or Konqueror, no matter whether you're on Linux, FreeBSD,
MacOSX, Cygwin, or QNX.

_____________________________________________________________________
Mathieu Bouchard -=- Montréal QC Canada -=- http://artengine.ca/matju
 
M

Mathieu Bouchard

class MyHash; include HollowHash # (3)
def each_key(&b)
# blah blah blah
end
# further blah
end
class MyVerifiedHash < MyHash; include SimpleHashP; end

woops, make that:


class MyHash; include HollowHash; include SimpleStringP # (3)
def each_key(&b)
# blah blah blah
end
# further blah
end
class MyVerifiedHash < MyHash; include SimpleHashP::Contract; end


SimpleHashP is the "declarator" module, that asserts that a given module
supports a feature set. SimpleHashP::Contract is a "verifier" module.
However there is currently still no hint that a given module _wants_ an
implementation of a given feature set in the way that HollowHash wants a
SimpleHashP without providing a default implementation of it.

_____________________________________________________________________
Mathieu Bouchard -=- Montréal QC Canada -=- http://artengine.ca/matju
 
M

Mathieu Bouchard

Mathieu Bouchard ha scritto:
yes, I know about metaruby but I supposed that it did not work with ruby
1.8. Anyway IIRC your metaruby has one thing I disliked,
since it does not provide a fine grained functionality splitting, I
mean, there are HollowIO and HollowHash instead of
Input+Output+Seekable+whatever and Map+EnumerableMap+whatever.
I may recall wrong, though.

From the source,

module SimpleIOP
include Readable, Seekable, Writable
module Contract; include ::Contract

but this might be something I changed after 0.7 (?). There is no split of
HollowIO into HollowReadableIO, etc, though.

However I do not know what's the difference between a Map and an
EnumerableMap as I can't think of a situation where it would be useful to
not define each_key, especially as it tends to be required for a lot of
Hash's functionality.

Similarly there is no distinction between ReadWriteHash and ReadOnlyHash
because common practice in Ruby is different: instead it is expected that
there will be #freeze and #frozen? and that exceptions will be thrown in
the cases where a write will be refused.
why those were bad name?

"Hollow" isn't quite as good as "Shallow" in the spatial metaphor domain,
as (almost all of) the Simple interfaces are not intended to be used
directly by the user, so it's better to think of a hole inside
(shallow) than a hole on (hollow).
FWIW I found names like SomethingP *crazy* the first time I read the
code, but now I think of them as lispy things and I can accept them :)

It's Lispy in the sense that Lisp also uses suffixes (p,q,f,!,?) but
MetaRuby's P is quite different from Lisp's P. I only use suffixes when i
want to have, for example, both an "interface" Foo, a "contract" Foo, a
"class" Foo, ... and i don't want to give them long boring names.
But I wonder: why both you and florian considered the need to have
contracts for this mixable things?

Because it helps distinguish responsibility in case of incorrect
behaviour, so that a bug can be located more easily. There's a similar
argument in favour of type-checking in general.

A way of working that many duck-typers have adopted is to just ignore
those things, so that all failures happen at the lowest possible level
instead of the highest possible one, producing error messages that are
meaningless in terms of the highest-level. Somehow they treat this as a
nonissue (and I don't want to speculate on why that is).

You wouldn't believe how many bugs in HollowArray (etc) I have catched by
just running contracts *and* unit-tests together at once. This is another
usual no-no, that is, most people polarise themselves as using one
technique or the other, but few accept to use both, with the notable
exception of Andy Hunt. I don't know whether he uses both at the _same_
time though. (note: i missed most of the last few years of ruby-talk so
maybe things have changed since)
I used to love this, and being scared from mixins like Enumerable wich
do not have checks, but now I feel this is very non-rubyish.

Do you feel you have to be rubyish?

How does writing rubyish code compare to writing good code? I mean, when
the two differ, what is the advantage of being more rubyish than good?

_____________________________________________________________________
Mathieu Bouchard -=- Montréal QC Canada -=- http://artengine.ca/matju
 
F

Florian Groß

Mathieu said:
class MyHash; include HollowHash; include SimpleStringP # (3)
def each_key(&b)
# blah blah blah
end
# further blah
end
class MyVerifiedHash < MyHash; include SimpleHashP::Contract; end

SimpleHashP is the "declarator" module, that asserts that a given module
supports a feature set. SimpleHashP::Contract is a "verifier" module.
However there is currently still no hint that a given module _wants_ an
implementation of a given feature set in the way that HollowHash wants a
SimpleHashP without providing a default implementation of it.

This sounds a lot like something that could be done with the Contracts
and implications in ruby-contract: (http://ruby-contract.rubyforge.org)

# You called this a declarator module
module Hash::MixIn
def each_keys(&block)
keys.each(&block)
end

def each(&block)
each_keys do |key|
block.call(key, self.fetch(key))
end
end

# ... and all the other methods
end

# You called this a verifier
class Hash::Contract < Contract
# My set again, though I wonder if .each_keys would not be a more
# efficient primitive than .keys. I'm still not sure about the
# difference between .remove and .delete.
provides :fetch, :store, :keys, :delete

# Here's a sample for a more complex provides:
provides :fetch do
@object.keys.each do |key|
# Contracts are really just disguised test suites
assert_no_exception { @object.fetch(key) }
end
end

implies Hash::MixIn
end

class MyHash
fulfills Hash::Contract
end

This contract does not yet do any detailed implementation testing, but
it ought to be easy to add.

Regarding FileAsString, that could be done like this:

class Object
adapt :to => String, :via => :read
end

Which would automatically add type adaption routes for all Objects that
implement #read. If there were a method like this expecting a string:

def open(string)
# implementation snipped...
end
signature :eek:pen, String, :result => File

And you were to call it as open(aFile) it would automatically read the
filename from aFile and then pass open() that as a String. As you can
see this is implicit type conversion which can be dangerous if you do it
too much. Therefore I think it should really only be used in cases where
the source and target are /very/ closely equivalent. One case would be
two libraries providing classes for passing around Colors with
incompatible interfaces.

The other things you wrote about also sounded very interesting. Perhaps
we can continue talking about these things via private mail or IRC as I
seem to be good at forgetting about slightly old threads on the ML...
 
F

Florian Groß

Mathieu said:
Because it helps distinguish responsibility in case of incorrect
behaviour, so that a bug can be located more easily. There's a similar
argument in favour of type-checking in general.

A way of working that many duck-typers have adopted is to just ignore
those things, so that all failures happen at the lowest possible level
instead of the highest possible one, producing error messages that are
meaningless in terms of the highest-level. Somehow they treat this as a
nonissue (and I don't want to speculate on why that is).

One thing I don't quite like about duck typing is that is applied where
it does not make sense to apply it.

Even the "If it quacks and walks like a duck it can be handled like a
duck" is an odd one. First of all duck typing never checks the "like a
duck" part. It also isn't correct to handle something that does a few
things like a duck like a duck. You can handle it like something that
does a few things like a duck. That's all you know.

The other area where I feel it is getting misused is calling
respond_to?() duck typing. I think that duck typing has always been
about not checking, but rather just doing.

I guess views will widely differ on this subject and I'm not even sure
if there is one authorative source that could clear all these different
interpretations up...
 

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,755
Messages
2,569,536
Members
45,020
Latest member
GenesisGai

Latest Threads

Top