[lnkForumImage]
TotalShareware - Download Free Software

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


 

Forums >

comp.lang.ruby

Hash#open! / Hash#close!

Trans

1/18/2007 1:13:00 PM

hi--

have a look:

class Hash

def open!
class << self
@_were_public = public_instance_methods - ['close!']
@_were_public.each { |m| private m }
def method_missing(s,*a)
if s.to_s[-1,1] == '='
self[s] = a.first
else
return self[s]
end
end
end
end

def close!
class << self
@_were_public.each { |m| public m }
@_were_public = nil
remove_method(:method_missing)
end
end

end

usage:

h = {a=>1, :sort_by=>2}
h.a #=> NoMethodError
h.sort_by #=> LocalJumpError
h.open!
h.a #=> 1
h.sort_by #=> 2
h.close!
h.a #=> NoMethodError
h.sort_by #=> LocalJumpError

thoughts? improvements? useful? bad-news?

T


ps. i came across an oddity while messing with this:

irb(main):019:0> q = {}
=> {}
irb(main):020:0> q.sort_by
=> []
irb(main):021:0> q[:a] = 1
=> 1
irb(main):022:0> q.sort_by
LocalJumpError: no block given
from (irb):22:in `sort_by'
from (irb):22
from :0
ruby 1.8.4 on debian


6 Answers

Robert Klemme

1/18/2007 1:31:00 PM

0

On 18.01.2007 14:12, Trans wrote:
> hi--
>
> have a look:
>
> class Hash
>
> def open!
> class << self
> @_were_public = public_instance_methods - ['close!']
> @_were_public.each { |m| private m }
> def method_missing(s,*a)
> if s.to_s[-1,1] == '='
> self[s] = a.first
> else
> return self[s]
> end
> end
> end
> end
>
> def close!
> class << self
> @_were_public.each { |m| public m }
> @_were_public = nil
> remove_method(:method_missing)
> end
> end
>
> end
>
> usage:
>
> h = {a=>1, :sort_by=>2}
> h.a #=> NoMethodError
> h.sort_by #=> LocalJumpError
> h.open!
> h.a #=> 1
> h.sort_by #=> 2
> h.close!
> h.a #=> NoMethodError
> h.sort_by #=> LocalJumpError
>
> thoughts? improvements? useful? bad-news?

What's the advantage over an OpenStruct?

irb(main):001:0> h = OpenStruct.new(:a=>1, :sort_by=>2)
=> #<OpenStruct sort_by=2, a=1>
irb(main):002:0> h.a
=> 1
irb(main):003:0> h.sort_by
=> 2

I'd probably rather make the logic from OpenStruct's constructor
available as update or merge method. But this is just a gut feeling.

> ps. i came across an oddity while messing with this:
>
> irb(main):019:0> q = {}
> => {}
> irb(main):020:0> q.sort_by
> => []
> irb(main):021:0> q[:a] = 1
> => 1
> irb(main):022:0> q.sort_by
> LocalJumpError: no block given
> from (irb):22:in `sort_by'
> from (irb):22
> from :0
> ruby 1.8.4 on debian

Same for Array. Not a big deal IMHO because the extra overhead of
checking for the block makes the average case (sorting non empty
collections) slower.

Kind regards

robert

Trans

1/18/2007 2:06:00 PM

0


Robert Dober wrote:

> Hmm is it really an oddity (I have the same behavior on 1.8.5/debian)?
> An empty hash sort just cannot apply the block to any elements, but I guess
> you know that ;).
> Would you prefer that sort_by just checks for the presence of a block
> anyway?

No, I think I'd rather it default to a nominal { |x| x } block, or
something. but yea, it's not a big deal, just sort of suprised me.

> I gotto think a lot about the rest you have written most interesting stuff,
> just some very quick thoughts
> * There are structs you know ;)
> * #open! could return self so that one can write h={:value=>42}.open!

good idea.

> #close! should than too for symmetry.
> * Would it not be better not to hide a method in case the hash has no key?
> That would be a mess right? One would not know if h.size is 1 because of
> h[:size]=1 or h={:a=>1}.open!
>
> * I'd like to use #open! behind the scenes with a block form like
> class Hash
> def use_opened &blk
> open!
> blk.call(self) if blk
> close!
> end
> end
> or even
> class Hash
> def in_context &blk
> open!
> instance_eval &blk
> .....
>
> Conclusion:
> Basically it is a nice idea but maybe you really want a struct.
> I would not think it should be in the core but in an extension like facet
> of course it might
> be spot on ;)

Good points. probably right about the struct. maybe i should eleborate
on why i thought of this at all... i like to be as flexiable as
possible, in my current project i have many methods that take a single
hash (or "ducked-hash") argument. i know what to expect in the argument
and i want to access the data with a method notation rather than
hash['key'] notation.

def amethod( hashything={} )
hashything.foo
end

it's not just a matter of presonal preference either. even if i left it
as a hash i would need to massage the data to make sure all the keys
are strings or symbols:

def amethod( hashything={} )
hashything = hashything.rekey(:to_s)
hashything['foo']
end

so either way i have to effect the argument in some manner. hence i
considered the idea of open!/close!

def amethod( hashything={} )
hashything.open!
hashything.foo
hashything.close!
end

as you suggested, a block form might be better.

of course i could just go back to conveting the hashything to an
OpenStruct (actually I use Facets' OpenObject) but i was just wondering
if maybe there's a nicer/easier/conciser way.

ideally, i wish i didn't have to even worry about this.

> In any case that is the kind of post which make this ML so much fun and
> education.

glad to hear :-)

t.


Ara.T.Howard

1/18/2007 3:43:00 PM

0

Trans

1/18/2007 4:16:00 PM

0


ara.t.howard@noaa.gov wrote:
> On Thu, 18 Jan 2007, Trans wrote:
>
> > hi--
> >
> > have a look:
> >
> > class Hash
> >
> > def open!
> > class << self
> > @_were_public = public_instance_methods - ['close!']
> > @_were_public.each { |m| private m }
> > def method_missing(s,*a)
> > if s.to_s[-1,1] == '='
> > self[s] = a.first
> > else
> > return self[s]
> > end
> > end
> > end
> > end
> >
> > def close!
> > class << self
> > @_were_public.each { |m| public m }
> > @_were_public = nil
> > remove_method(:method_missing)
> > end
> > end
> >
> > end
> >
> > usage:
> >
> > h = {a=>1, :sort_by=>2}
> > h.a #=> NoMethodError
> > h.sort_by #=> LocalJumpError
> > h.open!
> > h.a #=> 1
> > h.sort_by #=> 2
> > h.close!
> > h.a #=> NoMethodError
> > h.sort_by #=> LocalJumpError
> >
> > thoughts? improvements? useful? bad-news?
> >
> > T
>
> the only real disadvantage i see is that all these
>
> find_all
> keys
> []=
> each
> object_id
> singleton_methods
> inject
> delete
> value?
> to_hash
> equal?
> taint
> sort_by
> frozen?
> instance_variable_get
> max
> kind_of?
> each_pair
> respond_to?
> to_a
> delete_if
> merge!
> index
> select
> merge
> length
> type
> partition
> protected_methods
> store
> grep
> eql?
> instance_variable_set
> hash
> is_a?
> values
> reject
> to_s
> send
> default
> class
> size
> tainted?
> private_methods
> __send__
> member?
> default=
> default_proc
> untaint
> find
> each_with_index
> reject!
> id
> invert
> instance_eval
> collect
> inspect
> has_key?
> replace
> all?
> ==
> ===
> indexes
> entries
> clone
> public_methods
> extend
> each_value
> fetch
> detect
> freeze
> values_at
> zip
> display
> update
> __id__
> shift
> method
> has_value?
> empty?
> map
> =~
> methods
> clear
> any?
> rehash
> nil?
> sort
> dup
> indices
> key?
> min
> instance_variables
> include?
> []
> instance_of?
> each_key
>
>
> cannot be keys since method_missing is use to set the key. an impl like this
> could get around that

actually they are made private in my implementation (well, ones with
only alphanumeric characters) and that allows method_missing to get at
them -- yea, one of those esoteric peices of knowledge about ruby one
doesn't readily remember even when it is known.

T.


Erik Veenstra

1/19/2007 1:55:00 PM

0

What about this one? It uses my personal, small, generic
delegator.

There's at least one thing I don't like: Since it's a
delegator, operations on h2 directly affect h1. A pure
functional approach would be more appropriate: Going from h1 to
h2 results in a copy of the data. When un_delegate'ing, we
should once again copy the data. But that consumes a bit more
memory... ;]

(I'll work out the functional one and post it in a couple of
minutes...)

gegroet,
Erik V. - http://www.erikve...

----------------------------------------------------------------

module EV
class Delegator
def initialize(real_object)
@real_object = real_object
end

def method_missing(method_name, *args, &block)
@real_object.__send__(method_name, *args, &block)
end

def self.delegate(*args, &block)
res = self.new(*args)
end

def self.un_delegate(delegator_object)
delegator_object.instance_variable_get("@real_object")
end

def self.open(*args, &block)
res = delegate(*args)

if block
begin
block.call(res)
ensure
res = un_delegate(res)
end
end

res
end
end
end

class HashWithMethods < EV::Delegator
def method_missing(method_name, *args, &block)
method_name = method_name.to_s

if method_name =~ /=$/
key = method_name[0..-2]
value = args[0]

@real_object[key] = value
else
key = method_name

@real_object[key]
end
end
end

if __FILE__ == $0
h1 = {"a"=>111, "b"=>222}

HashWithMethods.open(h1) do |h2|
h2.b = 22222
h2.c = 33333
end

p h1
end

----------------------------------------------------------------


Erik Veenstra

1/19/2007 2:00:00 PM

0

> There's at least one thing I don't like: Since it's a
> delegator, operations on h2 directly affect h1. A pure
> functional approach would be more appropriate: Going from h1
> to h2 results in a copy of the data. When un_delegate'ing, we
> should once again copy the data. But that consumes a bit more
> memory... ;]
>
> (I'll work out the functional one and post it in a couple of
> minutes...)

That was easy... ;]

gegroet,
Erik V. - http://www.erikve...

----------------------------------------------------------------

--- hashy1.rb 2007-01-19 14:57:38.000000000 +0100
+++ hashy2.rb 2007-01-19 14:57:44.000000000 +0100
@@ -52,10 +52,12 @@
if __FILE__ == $0
h1 = {"a"=>111, "b"=>222}

- HashWithMethods.open(h1) do |h2|
+ h3 =
+ HashWithMethods.open(h1.dup) do |h2|
h2.b = 22222
h2.c = 33333
end

p h1
+ p h3
end

----------------------------------------------------------------