Forum: Ruby plz help with binding

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.
Ced5fff44ff8929fc974012ea108b284?d=identicon&s=25 Sergey Volkov (rf-vsv)
on 2006-05-11 07:37
Ruby guru, please help:

I'm creating variable-less storage, wich works fine:
#
def arr_fn() ; arr=[] ; lambda{ arr } ; end
fa = arr_fn ; fa[] << 10
fb = arr_fn ; fb[] << 20
p fa[] #=> [10]
p fb[] #=> [20]
# note that fa, fb have different binding for local arr - as expected

Now I'm trying to create instance-var-less storage:
#
class C
    def self.arr_meth( sym )
        arr = []
        define_method sym.to_sym, lambda{ arr }
    end
    arr_meth :arm
end
ca = C.new; ca.arm << 10
cb = C.new; cb.arm << 20
p ca.arm #-> [10, 20]
p cb.arm #-> [10, 20]
p( ca.arm.eql?( cb.arm ) ) #-> true
#
Why ca.arm, cb.arm share same local variable?
Is it bug or feature?

thanks
brs
Sergey
Cb48ca5059faf7409a5ab3745a964696?d=identicon&s=25 unknown (Guest)
on 2006-05-11 07:58
(Received via mailing list)
On Thu, 11 May 2006, Sergey Volkov wrote:


> cb = C.new; cb.arm << 20
> p ca.arm #-> [10, 20]
> p cb.arm #-> [10, 20]
> p( ca.arm.eql?( cb.arm ) ) #-> true
> #
> Why ca.arm, cb.arm share same local variable?
> Is it bug or feature?

because, in effect, this is what you've done

   class C
     ARR = []
     def arm() lambda{ ARM } end
   end

there are a lot of ways to do this but you want something like:

   harp:~ > cat a.rb
   class C
     def self.arr_meth am = nil
       @arr_meths ||= []
       @arr_meths << am.to_s if am
       @arr_meths
     end
     arr_meth :arm
     def initialize
       klass = self.class
       singleton_klass = class << self; self; end
       klass.arr_meth.each do |m|
         singleton_klass.module_eval do
           arr = []
           define_method m, lambda{ arr }
         end
       end
     end
   end

   ca = C.new; ca.arm << 10
   cb = C.new; cb.arm << 20

   p ca.arm #-> [10]
   p cb.arm #-> [20]
   p( ca.arm.eql?( cb.arm ) ) #-> false


   harp:~ > ruby a.rb
   [10]
   [20]
   false


neat idea btw.

hth.

-a
Cb48ca5059faf7409a5ab3745a964696?d=identicon&s=25 unknown (Guest)
on 2006-05-11 08:13
(Received via mailing list)
On Thu, 11 May 2006, Sergey Volkov wrote:

> Now I'm trying to create instance-var-less storage:

an even trickier way:

   harp:~ > cat a.rb
   class C
     def self.arr_meth m
       m = m.to_s
       define_method(m) do
         singleton_class =
           class << self
             self
           end
         singleton_class.module_eval do
           a = []
           define_method m, lambda{ a }
         end
         send m # recurse into newly defined method
       end
     end

     arr_meth :arm
   end

   ca = C.new; ca.arm << 10
   cb = C.new; cb.arm << 20

   p ca.arm #-> [10, 20]
   p cb.arm #-> [10, 20]
   p( ca.arm.eql?( cb.arm ) ) #-> true

   harp:~ > ruby a.rb
   [10]
   [20]
   false


-a
Ced5fff44ff8929fc974012ea108b284?d=identicon&s=25 Sergey Volkov (rf-vsv)
on 2006-05-11 09:20
(Received via mailing list)
Ara, thanks for your kindness,
but the whole idea was to make it simple, using same approach as with
lambda, without need to create any instance variable; in fact I'd like
to
implement mix-in (Module) with method to be executed in class context as
follows:
class A
    include WithLocalsMixIn
    # ?or may be
    # class << self ; include WithLocalsMixIn ; end
    with_locals( :v1, :v2 ){
        #??? initial bind for local vars :v1, :v2
        # sample only, no any practical use
        define_method :get_vars { [v1,v2] }
        define_method :set_vars { |a,b| v1, v2 = a,b }
    }
end
It seems to me this is impossible in Ruby (or inappropriate?)

Thanks, anyway, I got the trick in your solution: we have to create
metaclass instance methods during instance initialization.. br-r-r.. I
have
to practice to start thinking this way :)
And I still do not understand why def arr_fn creates binding, while def
self.arr_meth does not.
And why lambda does not work here:
#
class C
    def self.arr_meth( sym )
        lambda{
            arr = []
            define_method sym.to_sym, lambda{ arr }
        }[]
    end
    arr_meth :arm
end
#
my Lisp experience does help, Ruby is not functional enough and I'm
missing
Lisp macros :(

---

Just received your second message with 'trickier way': now I'm
wondering,
why trickier way is required, when simple lexical closure works
perfectly
and, as I understand, it's available in Ruby. Is Ruby closure true
closure?
Or does instance context ruins it? Or I misunderstand it totally?

I like Ruby very much, but so far I fail to understand very fundamental
language features :(
like this, for ex:
#
class StrWithPref < String ; def pref(n) self[0..n] end end
p StrWithPref.new("ABCD").pref(2).class
#
prints StrWithPref, not String as I expected;

well, as for now, I'm still leaning Ruby (and enjoy it a lot);
thanks to all for great Ruby community,
cheers
Sergey

----- Original Message -----
From: <ara.t.howard@noaa.gov>
To: "ruby-talk ML" <ruby-talk@ruby-lang.org>
Sent: Thursday, May 11, 2006 1:55 AM
Subject: Re: plz help with binding
Ced5fff44ff8929fc974012ea108b284?d=identicon&s=25 Sergey Volkov (rf-vsv)
on 2006-05-11 09:41
(Received via mailing list)
This is cool!
But not for average (read big enterprise) level programmer :(
How can I follow 'keep it simple, stupid!' approach with Ruby
metaprogramming?
How many Ruby programmers can reproduce this code with easy?
Can such code code be supported in production environment?

I'm not asking, I'm crying!
I'd like to ask Matz: give me real lexical closure and clean up meta
class
mess, please!!!

Or should we admit, that Ruby metaprogramming is for real programmers
only?
Or I'm just plain stupid?

Please advise;
thanks
bests
Sergey

----- Original Message -----
From: <ara.t.howard@noaa.gov>
To: "ruby-talk ML" <ruby-talk@ruby-lang.org>
Sent: Thursday, May 11, 2006 2:11 AM
Subject: Re: plz help with binding
93d566cc26b230c553c197c4cd8ac6e4?d=identicon&s=25 Pit Capitain (Guest)
on 2006-05-11 10:00
(Received via mailing list)
Sergey Volkov schrieb:
> cb = C.new; cb.arm << 20
> p ca.arm #-> [10, 20]
> p cb.arm #-> [10, 20]
> p( ca.arm.eql?( cb.arm ) ) #-> true
> #
> Why ca.arm, cb.arm share same local variable?
> Is it bug or feature?

Sergey, your code creates the local variable "arr" and the corresponding
lexical closure once for the class "C", so all instances of "C" share
the local variable. If you want to have a local variable per instance
without using instance variables, you have to define a closure per
instance:

  1  class C
  2    def self.arr_meth( sym )
  3      define_method sym.to_sym, lambda{
  4        arr = []
  5        class << self; self; end.instance_eval {
  6          define_method sym.to_sym, lambda{ arr }
  7        }
  8        arr
  9      }
10    end
11    arr_meth :arm
12  end

When you call "arr_meth" in line 11, the instance method is defined for
class "C", containing the code in lines 4..8. As in your original
implementation, this code is shared for all instances of "C".

Now, when you call this method for an instance for the first time, the
local variable is created in line 4. Lines 5..7 create the lexical
closure for this instance only by defining the method in the singleton
class of this instance.

Line 5 gets the singleton class of "self", returns it, and calls
"instance_eval" on it, which finally calls "define_method" in line 6.
You can't call "define_method" directly on the singleton class, because
it's a private method.

Regards,
Pit
Ced5fff44ff8929fc974012ea108b284?d=identicon&s=25 Sergey Volkov (rf-vsv)
on 2006-05-11 10:36
(Received via mailing list)
Sorry, guys,
I must admit - I was not right about Ruby closures;
most probably about metaprogramming neither,
so, please disregard my previous message;

closures work in Ruby perfectly, my mistake was that arr_meth was
executed
only once inside class definition:
#
class C
    def self.arr_meth( sym )
        arr = []
        define_method sym.to_sym, lambda{ arr }
    end
    arr_meth :arm
end
#
closure was created, but it was then shared between all class instances,
so
Ara was absolutely right - we have to create new closure for each
individual
instance, and this can not be implemented on class level (no instances
yet!);

I still do not want to modify class code to add instance_var-less
storage,
so at this moment I have implemented external function to extend class
with
new method (thanks, Ara!):
#!/bin/ruby
module M
    def self.add_loc obj, name, ini=[]
        skl = class << obj ; self ; end
        skl.module_eval{
           v = ini
           define_method name.to_sym, lambda{ v }
        }
    end
end
#
ca = Object.new
M.add_loc ca,:pocket; ca.pocket << 10
M.add_loc ca,:bag; ca.bag << :book
#
cb = Object.new
M.add_loc cb,:pocket; cb.pocket << 20
M.add_loc cb,:bag; cb.bag << :apple
#
p [ca.pocket, ca.bag]
p [cb.pocket, cb.bag]
---------- Capture Output ----------
[[10], [:book]]
[[20], [:apple]]

Hidden instance slots created! In this example they contain array, but
accessors can be implemented without any problem.
Any ideas about better API?
thanks
regards
Sergey
--
oh - please do not forget to be kind!

----- Original Message -----
From: "Sergey Volkov" <gm.vlkv@gmail.com>
To: "ruby-talk ML" <ruby-talk@ruby-lang.org>
Sent: Thursday, May 11, 2006 3:39 AM
Subject: Re: plz help with binding
Ced5fff44ff8929fc974012ea108b284?d=identicon&s=25 Sergey Volkov (rf-vsv)
on 2006-05-11 11:13
(Received via mailing list)
thanks all for your patience,
Ara, Pit for your help;

here is my solution, if you are interested,
now you can get rid of instance variables in your Ruby code :)
#
module M
    def add_loc name, reader_only=false, ini=nil
        skl = class << self ; self ; end
        skl.module_eval{
           v = ini
           define_method name.to_sym, lambda{ v }
           define_method "#{name}=".to_sym, lambda{ |a| v = a } unless
reader_only
        }
    end
end
#
class CA ; include M ; end
ca = CA.new
ca.add_loc :pocket, true, [] # reader only
ca.add_loc :bag
ca.pocket << 10
ca.bag = :book
#
class CB ; include M ; end
cb = CB.new
cb.add_loc :address_book, true, {}; cb.address_book["me"] = "my address"
cb.add_loc :bag; cb.bag = :apple
#
p [ca.pocket, ca.bag]
p [cb.address_book, cb.bag]
#
---------- Capture Output ----------
[[10], :book]
[{"me"=>"my address"}, :apple]

regards
Sergey
--
There are 10 kinds of people in the world; those who know binary, and
those
who don't.

----- Original Message -----
From: "Sergey Volkov" <gm.vlkv@gmail.com>
To: "ruby-talk ML" <ruby-talk@ruby-lang.org>
Sent: Thursday, May 11, 2006 4:33 AM
Subject: Re: plz help with binding
Ffcb418e17cac2873d611c2b8d8d891c?d=identicon&s=25 unknown (Guest)
on 2006-05-11 12:05
(Received via mailing list)
>
> cb = C.new; cb.arm << 20
> p ca.arm #-> [10, 20]
> p cb.arm #-> [10, 20]
> p( ca.arm.eql?( cb.arm ) ) #-> true
> #
> Why ca.arm, cb.arm share same local variable?
> Is it bug or feature?

:) You're mad :) But I get you... I think.

ca.arm and cb.arm share the same local because they're the same
function.

In the first example, you're creating a new lambda, that holds a new
array, with each call to "arr_fn".

In the second example, you're only creating the method "arr" inside the
class B once (with the line "arr_meth : arm"). This method (which is
sitting in the class B, not in each instance of class B - the instances
_share_ it), is what you're later calling, and appending to.

:) Totally mad, but thanks, it's a good mind bender.

[I really don't mean to sound insulting, by the way, I think you're
doing something impressively crazy there - it reminds me of "boxes" in
lisp (I think "boxes" is the right term).]
Ffcb418e17cac2873d611c2b8d8d891c?d=identicon&s=25 unknown (Guest)
on 2006-05-11 12:11
(Received via mailing list)
> thanks all for your patience,
> Ara, Pit for your help;
>
> here is my solution, if you are interested,
> now you can get rid of instance variables in your Ruby code :)

:) Doesn't this still suffer from the previous problem though? The
"variable" is stored per class, rather than per instance?
93d566cc26b230c553c197c4cd8ac6e4?d=identicon&s=25 Pit Capitain (Guest)
on 2006-05-11 12:54
(Received via mailing list)
Sergey Volkov schrieb:
> closure was created, but it was then shared between all class instances,
> so Ara was absolutely right - we have to create new closure for each
> individual instance, and this can not be implemented on class level (no
> instances yet!);

Sergey, maybe I don't understand what you mean by "implemented on class
level", but as I've tried to show you with the code in my previous post
you *can* implement such local variables at the class level. Here's the
output, just in case you haven't tried:

   class C
     ...
     arr_meth :arm
   end

   ca = C.new; ca.arm << 10
   cb = C.new; cb.arm << 20

   p ca.arm                # => [10]
   p cb.arm                # => [20]
   p ca.arm.eql?( cb.arm ) # => false

Regards,
Pit
Ced5fff44ff8929fc974012ea108b284?d=identicon&s=25 Sergey Volkov (rf-vsv)
on 2006-05-11 16:35
(Received via mailing list)
----- Original Message -----
From: <benjohn@fysh.org>
Sent: Thursday, May 11, 2006 6:08 AM
Subject: Re: plz help with binding


>> here is my solution, if you are interested,
>> now you can get rid of instance variables in your Ruby code :)
>
> :) Doesn't this still suffer from the previous problem though? The
> "variable" is stored per class, rather than per instance?

Do you mean singleton class?
If so, then it's the same as per instance.
But I need even more - variable stored per add_loc call, I thought I
made
it, here is how:

v = ini is executed every time add_loc is called, so every time new
binding
for local variable v is created, which is referenced in accessor
methods;
I can move assignment out of module_eval scope, result is the same:
#
module M
    def add_loc name, reader_only=false, ini=nil
        skl = class << self ; self ; end
        v = ini # create new local variable binding
        skl.module_eval{
           define_method name.to_sym, lambda{ v } # reader
           define_method "#{name}=".to_sym, lambda{ |a| v = a } unless
reader_only
        }
    end
end
#
class CC ; include M ; end
ca = CC.new
ca.add_loc :pocket, true, [] # array, reader only
ca.add_loc :bag
ca.pocket << 10
ca.bag = :book
#
cb = CC.new
cb.add_loc :address_book, true, {} # hash, reader only
cb.add_loc :bag
cb.address_book["me"] = "my address"
cb.bag = :apple
#
p [ca.pocket, ca.bag]
p [cb.address_book, cb.bag]
---------- Capture Output ----------
[[10], :book]
[{"me"=>"my address"}, :apple]
> Terminated with exit code 0.

what am I missing here?
Cb48ca5059faf7409a5ab3745a964696?d=identicon&s=25 unknown (Guest)
on 2006-05-11 17:07
(Received via mailing list)
On Thu, 11 May 2006, Sergey Volkov wrote:

> closure was created, but it was then shared between all class instances, so
> Ara was absolutely right - we have to create new closure for each individual
> instance, and this can not be implemented on class level (no instances
> yet!);

yes exactly - you must defer creation of the closure somehow.  it must
be an
'instance closure' instead of an 'instance variable' - we can get away
from
doing something at the instance level otherwise they all share the same
'thing' ;-)

> I still do not want to modify class code to add instance_var-less storage,
> so at this moment I have implemented external function to extend class with
> new method (thanks, Ara!):

> #!/bin/ruby
> module M
>   def self.add_loc obj, name, ini=[]
>       skl = class << obj ; self ; end
>       skl.module_eval{
>          v = ini
>          define_method name.to_sym, lambda{ v }
>       }
>   end
> end

yes, you are getting it - must be the lisp background, it took me a long
time
to get this stuff!

> Any ideas about better API?

use keywords - default paramerters are pure evil :

   some_method(42,true,true,true,false,2)     #=> YUK

vs

   some_method 42 :async => true, :block_size #=> 2  # AHHH ;-)

so, for instance:

     harp:~ > cat a.rb
     class Object
       def singleton_class &b
         sc =
           class << self
             self
           end
         sc.module_eval &b if b
         sc
       end
     end

     class Module
       def vattr m, opts = {}, &b
         m = m.to_s

         reader = opts['reader'] || opts[:reader]
         writer = opts['writer'] || opts[:writer]
         query = opts['query'] || opts[:query]

         accessor = ![reader, writer].any?

         reader = "#{ m }" if reader or accessor
         writer = "#{ m }=" if writer or accessor
         query = "#{ m }?" if query or accessor

         define_method(reader) do
           singleton_class do
             val = b.call if b
             define_method reader, lambda{ val }
           end
           send reader
         end if reader

         define_method(writer) do |val|
           singleton_class do
             vattr(reader){ val }
           end
           send reader
         end if writer

         define_method(query) do
           not not send reader
         end if query

         [reader, writer, query]
       end
       def vattr_r m, opts = {}, &b
         opts.update 'reader' => true
         vattr m, opts, &b
       end
       def vattr_w m, opts = {}, &b
         opts.update 'writer' => true
         vattr m, opts, &b
       end
     end

     class C
       vattr('va'){ [] }
     end

     ca = C.new; ca.va << 10
     cb = C.new; cb.va << 20

     p ca.va #-> [10]
     p cb.va #-> [10]

     ca.va = {}
     cb.va = {}

     ca.va.update 'k' => 4
     cb.va.update 'k' => 2

     p ca.va? #-> true
     p ca.va #-> {"k"=>4}

     p cb.va? #-> true
     p cb.va #-> {"k"=>2}



     harp:~ > ruby a.rb
     [10]
     [20]
     true
     {"k"=>4}
     true
     {"k"=>2}



regards.

-a
This topic is locked and can not be replied to.