Hash#open! / Hash#close!

T

Trans

hi--

have a look:

class Hash

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

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

end

usage:

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

thoughts? improvements? useful? bad-news?

T


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

irb(main):019:0> q =3D {}
=3D> {}
irb(main):020:0> q.sort_by
=3D> []
irb(main):021:0> q[:a] =3D 1
=3D> 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
 
R

Robert Klemme

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 = a.first
else
return self
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
 
T

Trans

Robert said:
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.
 
A

ara.t.howard

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 = a.first
else
return self
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

harp:~ > cat a.rb
class Hash
class Proxy
alias_method '__instance_eval__', 'instance_eval'

instance_methods.each{|m| undef_method m unless m[/__/]}

def initialize h, &b
@h = h
__instance_eval__ &b if b
end

def method_missing m, *a, &b
k = m.to_s.delete '=!?'
case a.size
when 0
@h[k]
when 1
@h[k] = a.shift
else
@h[k] = a
end
end
end

def open! &b
proxy = Proxy.new self, &b
b ? self : proxy
end
end

h = { 'k' => 'v' }
p h

h.open!{ k 42 }
p h

h.open!{ self.a = 42.0 }
p h

proxy = h.open!
proxy.b = 'forty-two'
p h

h.open!{ sort 'can_be_set' }
p h

harp:~ > ruby a.rb
{"k"=>"v"}
{"k"=>42}
{"k"=>42, "a"=>42.0}
{"k"=>42, "a"=>42.0, "b"=>"forty-two"}
{"k"=>42, "a"=>42.0, "b"=>"forty-two", "sort"=>"can_be_set"}


neat idea though.

-a
 
T

Trans

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 = a.first
else
return self
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.
 
E

Erik Veenstra

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.erikveen.dds.nl/

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

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

----------------------------------------------------------------
 
E

Erik Veenstra

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.erikveen.dds.nl/

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

--- 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

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

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

No members online now.

Forum statistics

Threads
474,431
Messages
2,571,679
Members
48,796
Latest member
Greg L.

Latest Threads

Top