Plz help with binding


#1

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


#2

On Thu, 11 May 2006, Sergey V. 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


#3

On Thu, 11 May 2006, Sergey V. 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


#4

This is cool!
But not for average (read big enterprise) level programmer :frowning:
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: removed_email_address@domain.invalid
To: “ruby-talk ML” removed_email_address@domain.invalid
Sent: Thursday, May 11, 2006 2:11 AM
Subject: Re: plz help with binding


#5

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


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 :frowning:
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: removed_email_address@domain.invalid
To: “ruby-talk ML” removed_email_address@domain.invalid
Sent: Thursday, May 11, 2006 1:55 AM
Subject: Re: plz help with binding


#6

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 V.” removed_email_address@domain.invalid
To: “ruby-talk ML” removed_email_address@domain.invalid
Sent: Thursday, May 11, 2006 3:39 AM
Subject: Re: plz help with binding


#7

Sergey V. 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


#8

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?

:slight_smile: You’re mad :slight_smile: 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.

:slight_smile: 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).]


#9

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

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 V.” removed_email_address@domain.invalid
To: “ruby-talk ML” removed_email_address@domain.invalid
Sent: Thursday, May 11, 2006 4:33 AM
Subject: Re: plz help with binding


#10

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

:slight_smile: Doesn’t this still suffer from the previous problem though? The
“variable” is stored per class, rather than per instance?


#11

Sergey V. 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


#12

On Thu, 11 May 2006, Sergey V. 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’ :wink:

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

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


#13

----- Original Message -----
From: removed_email_address@domain.invalid
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 :slight_smile:

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