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.
Trans (Guest)
on 2007-01-18 15: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
Robert D. (Guest)
on 2007-01-18 15:35
(Received via mailing list)
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 ;)
* #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
Robert K. (Guest)
on 2007-01-18 15: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
Trans (Guest)
on 2007-01-18 16:07
(Received via mailing list)
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 ;)
> * #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.
unknown (Guest)
on 2007-01-18 17: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
Trans (Guest)
on 2007-01-18 18:19
(Received via mailing list)
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.
Erik V. (Guest)
on 2007-01-19 17: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
Robert D. (Guest)
on 2007-01-19 17:30
(Received via mailing list)
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
Erik V. (Guest)
on 2007-01-19 17: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.