Callback


(Han Holl) #1

Hi,

The following code works, but I was wondering if the creation of the @obj
instance variable is really necessary?

class A
def callback(name, obj)
@obj = obj
eval(“def #{name}; @obj.keys; end”)
end
end

class B
attr_reader :keys
def initialize(k, cb)
@keys = k
cb.callback(‘cassette’, self)
end
end

a = A.new
g = B.new([1,2,3], a)
puts a.cassette


(ts) #2

The following code works, but I was wondering if the creation of the @obj
instance variable is really necessary?

The problem is not with the instance variable, but with the evil eval :slight_smile:

You don't prefer this ?

svg% cat b.rb
#!/usr/bin/ruby
class A
end

class B
   def initialize(k, cb)
      class << cb; self end.instance_eval do
         define_method('cassette') { k }
      end
   end
end
a = A.new
g = B.new([1,2,3], a)
puts a.cassette
svg%

svg% b.rb
1
2
3
svg%

Guy Decoux


(Greg Millam) #3

Received: Fri, 27 Feb 2004 02:29:48 +0900
And lo Han wrote:

class A
def callback(name, obj)
@obj = obj
eval(“def #{name}; @obj.keys; end”)
end
end

class B
attr_reader :keys
def initialize(k, cb)
@keys = k
cb.callback(‘cassette’, self)
end
end

a = A.new
g = B.new([1,2,3], a)
puts a.cassette

class A
def initialize
@cb_hash = Hash.new(nil)
end
def callback(name,&block)
@cb_hash[name.to_sym] = block if block_given?
end
def method_missing(name,*args)
return @cb_hash[name].call(*args) if @cb_hash.has_key?(name)
end
end

a = A.new
a.callback(“mykeys”) {
[1,2,3]
}

puts a.mykeys.join(’, ') #=> 1, 2, 3

class B
def initialize(k,cb)
cb.callback(“cassette”) { k }
end
end

b = B.new([4,5,6],a)

puts a.cassette.join(’, ') #=> 4, 5, 6

Personally, I think writing a caller in class A (i.e a.call(“cassette”).join) instead of method missing makes more sense, but it’s up to you.

Options left to the reader: allow ‘callback’ to accept blocks and non-blocks, and have method_missing (or call()) act differently depending on if it’s a proc block or not: i.e returning the value instead of calling it.

I hope this helped,

  • Greg

(Pit) #4

Han Holl wrote:

The following code works, but I was wondering if the creation of the @obj
instance variable is really necessary?

No, you don’t need the instance variable (see below).

class A
def callback(name, obj)

Original version:

@obj = obj
eval("def #{name}; @obj.keys; end")

Alternative 1:
self.class.send :define_method, name, proc { obj.keys }

Alternative 2:
class << self; self; end.send :define_method, name, proc { obj.keys }

end
end

class B
attr_reader :keys
def initialize(k, cb)
@keys = k
cb.callback(‘cassette’, self)
end
end

a = A.new
g = B.new([1,2,3], a)
puts a.cassette

Alternative 1 behaves more or less like your original version. The drawback of
both of them is that the method is defined in class A, but works only in one
instance of A.

Alternative 2 defines the method only in the one instance passed to callback.

Add

puts A.new.cassette

to the tests to see the difference.

Regards,
Pit


(Robert) #5

“Han Holl” han.holl@pobox.com schrieb im Newsbeitrag
news:b93fa83f.0402260928.25ffbd4a@posting.google.com

Hi,

The following code works, but I was wondering if the creation of the
@obj
instance variable is really necessary?

It is not. I’d consider it harmful since you will have problems defining
more than one callback this way. Solutions that others have proposed are
far betters since they store ‘obj’ in a closure. So you can invoke
#callback arbitrarily often.

I wonder though what you are trying to achieve. It looks quite
complicated to me and currently I don’t have a clue about the real world
problem you are trying to solve.

Regards

robert
···

class A
def callback(name, obj)
@obj = obj
eval(“def #{name}; @obj.keys; end”)
end
end

class B
attr_reader :keys
def initialize(k, cb)
@keys = k
cb.callback(‘cassette’, self)
end
end

a = A.new
g = B.new([1,2,3], a)
puts a.cassette


(Han Holl) #6

ts decoux@moulon.inra.fr wrote in message news:200402261738.i1QHcl815264@moulon.inra.fr

The following code works, but I was wondering if the creation of the @obj
instance variable is really necessary?

The problem is not with the instance variable, but with the evil eval :slight_smile:

You don’t prefer this ?

svg% cat b.rb
#!/usr/bin/ruby
class A
end

class B
def initialize(k, cb)
class << cb; self end.instance_eval do
define_method(‘cassette’) { k }
end
end
end
a = A.new
g = B.new([1,2,3], a)
puts a.cassette
svg%

svg% b.rb
1
2
3
svg%

Oh, yes. I prefer that very much.
But is instance_eval not also an ‘evil eval’ ?

And define_method isn’t in my Pickaxe, nor in ri-0.8a-1, nor in the
doc directory of ruby-1.8.0 not in the ChangeLog.
It does appear in doc/Changelog-1.8.0 of ruby-1.8.1. Strange.

And I kind of wonder about the ‘class << cb; self end.’ construction.
Wouldn’t ‘cb.class.’ achieve the same ? Both ‘b.class.class’ and
’class << b; self end.class’ return Class in irb.

Many thanks to you, and to Gregory and Pit, for the helpful reactions.


(Han Holl) #7

ts decoux@moulon.inra.fr wrote in message

class B
def initialize(k, cb)
class << cb; self end.instance_eval do
define_method(‘cassette’) { k }
end
end
end

Hi,

I’m still having a problem with this: The above works, but what I’m
trying to do is something like
class B
attr_reader :whatsit
def initialize(k, cb)
class << cb; self end.instance_eval do
define_method(‘cassette’) { ‘somehow_return_the_b.objects_whatsit’ }
end
end
end

The local variable ‘k’ seems to be in scope inside the instance eval, but
instance variables or class B methods aren’t.
Still, with Pit’s solution I got rid of the instance variable, and of the
eval.

O yes, please forget my question about cb << class; self end and cb.class.
It’s still early here

Cheers,

Han Holl


(Han Holl) #8

Pit Capitain pit@capitain.de wrote in message

Alternative 1:
self.class.send :define_method, name, proc { obj.keys }

Alternative 2:
class << self; self; end.send :define_method, name, proc { obj.keys }

[ cut ]

Alternative 1 behaves more or less like your original version. The drawback of
both of them is that the method is defined in class A, but works only in one
instance of A.

Alternative 2 defines the method only in the one instance passed to callback.

Thanks. This was what I wanted. In my case the class to add the ‘cassette’ method
to is a singleton, so I guess alternative 1 would be the most efficient.

Cheers,

Han Holl


(Han Holl) #9

“Robert Klemme” bob.news@gmx.net wrote in message >

I wonder though what you are trying to achieve. It looks quite
complicated to me and currently I don’t have a clue about the real world
problem you are trying to solve.

You’re right, it’s quite complicated.

We had a data-entry program where people who weren’t necessarily professional
programmers could write end-of-text-field validation functions in a very simple
programming language that looked a lot like awk, but with multidimensional
associative arrays.
We are going to replace this language with a subset of ruby.
I am trying to create an environment that resembles the environment they were
used to. They will be able to write functions that are all evaluated into
the same class. These ruby functions will be called with a lot of environment
predefined. A typical end-function could look like:
def date_ok(value)
value =~ /\d\d-\d\d-\d\d/
end
These end-functions are alse able to fill-in other fields, do code expansion
and so on.
In an configuration file you can specify which functions will be called when.
An important part of this is to make it look as familiar to them as I can,
but still have the advantage of using ruby with all of it’s wondeful features.

Cheers,

Han Holl


(ts) #10

But is instance_eval not also an 'evil eval' ?

   [].instance_eval("1+2") is the evil eval

   [].instance_eval { 1 + 2 } is not the evil eval

:slight_smile:

Guy Decoux


(Ian Macdonald) #11

Actually, I was looking forward to an answer to that question, because
the same thing occurred to me, namely that the whole clause could be
replaced by ‘cb.class’.

Why is that not the case?

Ian

···

On Fri 27 Feb 2004 at 18:39:47 +0900, Han Holl wrote:

O yes, please forget my question about cb << class; self end and cb.class.
It’s still early here


Ian Macdonald | Actresses will happen in the best regulated
System Administrator | families. – Addison Mizner and Oliver
ian@caliban.org | Herford, “The Entirely New Cynic’s
http://www.caliban.org | Calendar”, 1905
>


(Robert) #12

“Han Holl” han.holl@pobox.com schrieb im Newsbeitrag
news:b93fa83f.0402271401.74158b0a@posting.google.com

“Robert Klemme” bob.news@gmx.net wrote in message >

I wonder though what you are trying to achieve. It looks quite
complicated to me and currently I don’t have a clue about the real world
problem you are trying to solve.

You’re right, it’s quite complicated.

Aha… :slight_smile:

I need to repeat your statements to be sure I got you correctly. :slight_smile:

We had a data-entry program where people who weren’t necessarily
professional
programmers could write end-of-text-field validation functions in a very
simple
programming language that looked a lot like awk, but with multidimensional
associative arrays.
We are going to replace this language with a subset of ruby.

So, people will write the functions using Ruby sytax in the future?

I am trying to create an environment that resembles the environment they
were
used to. They will be able to write functions that are all evaluated into
the same class.

If I do understand correctly, you end up with a single class that has a
bunch of methods and these methods are the ones your people will write.

These ruby functions will be called with a lot of environment
predefined. A typical end-function could look like:
def date_ok(value)
value =~ /\d\d-\d\d-\d\d/
end

This one apparently doesn’t use any environmental data. (Btw, you might
want to add “^” and “$” to that function.)

These end-functions are alse able to fill-in other fields, do code
expansion
and so on.

Aha, so the instance of your class at hand has instance variables and
predefined methods that some of the functions can use.

In an configuration file you can specify which functions will be called
when.
An important part of this is to make it look as familiar to them as I can,
but still have the advantage of using ruby with all of it’s wondeful
features.

So, basically there are two interfaces for your people: the piece where they
define the functions and the config file where they define the concrete
invocation order of functions.

And it seems, you want to keep the config file syntax while changing the
sytax that the function definition interface (or file?) uses to Ruby. So
basically you parse the config file and generate a method that reflects
invocation sequence of the other methods. Something like that.

I guess, I got the broad picture. Now I’m just missing the connection to
the original problem. :slight_smile:

Regards

robert

(ts) #13

Actually, I was looking forward to an answer to that question, because
the same thing occurred to me, namely that the whole clause could be
replaced by 'cb.class'.

cb.class give the class of `cb'

class << cb; self end give the singleton class associated with `cb'

For example

svg% cat b.rb
#!/usr/bin/ruby
cb = []

cb.class.instance_eval { define_method("aa") { p "nice method aa for Array" }}
class << cb; self end.instance_eval do
   define_method("bb") { p "nice method bb for cb" }
end

cb.aa
cb.bb
[1].aa
[1].bb
svg%

svg% b.rb
"nice method aa for Array"
"nice method bb for cb"
"nice method aa for Array"
./b.rb:12: undefined method `bb' for [1]:Array (NoMethodError)
svg%

Guy Decoux


(Han Holl) #14

“Robert Klemme” bob.news@gmx.net wrote in message

So, people will write the functions using Ruby sytax in the future?
That’s correct. One of the greatest advantages is that ruby is very good at
pinpointing errors. Our previous language was ‘forgiving’ in the awk/perl
tradition. Could be tough to debug.

def date_ok(value)
value =~ /\d\d-\d\d-\d\d/
end

This one apparently doesn’t use any environmental data. (Btw, you might
want to add “^” and “$” to that function.)

You’re right of cource. It was just a quick example of the level of expertise
needed to write such functions.
[ cut]

Aha, so the instance of your class at hand has instance variables and
predefined methods that some of the functions can use.

And it seems, you want to keep the config file syntax while changing the
sytax that the function definition interface (or file?) uses to Ruby. So
basically you parse the config file and generate a method that reflects
invocation sequence of the other methods. Something like that.

The configuration file is a xml file that also describes the layout of the
different tabs and subscreens. This is a client/server system with Windows
clients and a Linux server.

I guess, I got the broad picture. Now I’m just missing the connection to
the original problem. :slight_smile:

Now it gets even more complicated: the old system had a special kind of field
for repeated groups of fields. Kind of a matrix. On screen there was room for
one row at a time, the first row item acted as the key. This field had a
name, say ‘cassettes’, and it was exposed to the end-functions as a two
dimensional array: cassette[row][col]. The titles were exposed as
cassette_cols[], and the keys as cassette_rows[]. This is what I tried to
do. An end-function can fill on or more rows (and maybe display one on the
screen).
With the above trick, the same exposure can be achieved in ruby.

Hope I haven’t been to unclear: it’s all about changing as little as possible
for the people who have to work with the new system.

Cheers,

Han Holl


(Robert) #15

“Han Holl” han.holl@pobox.com schrieb im Newsbeitrag
news:b93fa83f.0402280130.3c088728@posting.google.com

Hope I haven’t been to unclear: it’s all about changing as little as
possible
for the people who have to work with the new system.

Thank’s for your patience! It’s just always interesting with what people
out there have to deal and especially how Ruby’s features help them get
their job done gracefully.

Good luck for your project!

robert