Hash#open! / Hash#close!


#1

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


#2

On 1/18/07, Trans removed_email_address@domain.invalid wrote:

=> []
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

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?

I gotto think a lot about the rest you have written most interesting
stuff,
just some very quick thoughts

  • There are structs you know :wink:
  • #open! could return self so that one can write h={:value=>42}.open!
    #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 :wink:

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

Cheers
Robert


#3

On 18.01.2007 14:12, Trans wrote:

    def method_missing(s,*a)
  class << self

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)
=> #
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.

    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


#4

Robert D. 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 :wink:
  • #open! could return self so that one can write h={:value=>42}.open!

good idea.

         close!

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

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

t.


#5

On Thu, 18 Jan 2007, Trans wrote:

   def method_missing(s,*a)
 class << self

h = {a=>1, :sort_by=>2}

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


#6

removed_email_address@domain.invalid wrote:

   @_were_public = public_instance_methods - ['close!']

usage:

 object_id
 max
 type
 to_s
 untaint
 all?
 freeze
 =~
 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.


#7

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


#8

On 1/18/07, Trans removed_email_address@domain.invalid wrote:

   end

h.close!
keys
sort_by
select
is_a?
member?
inspect
each_value
has_value?
indices
this
Just an idea about that, is there already an RCR out about method
wrappers.
I’ll browse the archives just thought that you’d probably know.

Cheers
Robert


#9

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