Forum: Ruby Hash#open! / Hash#close!

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
45196398e9685000d195ec626d477f0e?d=identicon&s=25 Trans (Guest)
on 2007-01-18 14:13
(Received via mailing list)
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
703fbc991fd63e0e1db54dca9ea31b53?d=identicon&s=25 Robert Dober (Guest)
on 2007-01-18 14:35
(Received via mailing list)
On 1/18/07, Trans <transfire@gmail.com> 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 ;)
* #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 ;)


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

Cheers
Robert
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2007-01-18 14:37
(Received via mailing list)
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)
=> #<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.

>         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
45196398e9685000d195ec626d477f0e?d=identicon&s=25 Trans (Guest)
on 2007-01-18 15:07
(Received via mailing list)
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!
>   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.
Cb48ca5059faf7409a5ab3745a964696?d=identicon&s=25 unknown (Guest)
on 2007-01-18 16:43
(Received via mailing list)
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
45196398e9685000d195ec626d477f0e?d=identicon&s=25 Trans (Guest)
on 2007-01-18 17:19
(Received via mailing list)
ara.t.howard@noaa.gov 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.
6b0967f63d03e99b6c07a3f5ed224c77?d=identicon&s=25 Erik Veenstra (Guest)
on 2007-01-19 16:29
(Received via mailing list)
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
703fbc991fd63e0e1db54dca9ea31b53?d=identicon&s=25 Robert Dober (Guest)
on 2007-01-19 16:30
(Received via mailing list)
On 1/18/07, Trans <transfire@gmail.com> 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
6b0967f63d03e99b6c07a3f5ed224c77?d=identicon&s=25 Erik Veenstra (Guest)
on 2007-01-19 16:31
(Received via mailing list)
> 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
This topic is locked and can not be replied to.