Generating Functions in Ruby

A

Andreas Lundgren

Hi!

I'm looking for a construction in Ruby to generate a (big) set of
functions. I need these functions to be class methods and called by
unique names since they are called via an external API.

As an example, let me create two functions that hard codes an addition
and then uses this in a small test program.

def FivePlusFour
return 5+4
end

def OnePlusSeven
return 1+7
end

puts("Five + Four is " + FivePlusFour().to_s)
puts("One + Seven is " + OnePlusSeven().to_s)


I would like a way to generate the methods in runtime. As an old C-
programmer, I'm looking for something like this:

# Define a macro
define SUM_TEMPLATE(namn,x,y) \\
def namn \\
return x+y \\
end

# Create each function with a one-liner in runtime.
SUM_TEMPLATE(FivePlusFour, 5, 4)
SUM_TEMPLATE(OnePlusSeven, 1, 7)

# Test program
puts("Five + Four is " + FivePlusFour().to_s)
puts("One + Seven is " + OnePlusSeven().to_s)

Is this possible in Ruby?

(Please don't reply how to solve this silly example in a better way,
the true use case is of cause much more complex. But conceptually it's
the same.)

Best Regards,
Andreas Lundgren - Sweden
 
S

Steve Klabnik

[Note: parts of this message were removed to make it a legal post.]

What you want is define_method. Ill gives you some links when I'm not on my
phone.
 
R

Robert Klemme

Hi!

I'm looking for a construction in Ruby to generate a (big) set of
functions. I need these functions to be class methods and called by
unique names since they are called via an external API.

As an example, let me create two functions that hard codes an addition
and then uses this in a small test program.

def FivePlusFour
=A0return 5+4
end

def OnePlusSeven
=A0return 1+7
end

puts("Five + Four is " + FivePlusFour().to_s)
puts("One + Seven is " + OnePlusSeven().to_s)


I would like a way to generate the methods in runtime. As an old C-
programmer, I'm looking for something like this:

# Define a macro
define SUM_TEMPLATE(namn,x,y) \\
def namn \\
=A0return x+y \\
end

# Create each function with a one-liner in runtime.
SUM_TEMPLATE(FivePlusFour, 5, 4)
SUM_TEMPLATE(OnePlusSeven, 1, 7)

# Test program
puts("Five + Four is " + FivePlusFour().to_s)
puts("One + Seven is " + OnePlusSeven().to_s)

Is this possible in Ruby?

def SUM_TEMPLATE(name, x, y)
class <<self;self;end.class_eval "def #{name}; #{x} + #{y}; end"
end
(Please don't reply how to solve this silly example in a better way,
the true use case is of cause much more complex. But conceptually it's
the same.)

Is it really? Do you always have one name and two values as input?
Can you give a bit more contextual information?

Cheers

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
7

7stud --

Robert K. wrote in post #999483:
def SUM_TEMPLATE(name, x, y)
class <<self;self;end.class_eval "def #{name}; #{x} + #{y}; end"
end

...of what class? Perhaps something like this:


def SUM_TEMPLATE(the_class, meth_name, x, y)
class_obj = Object.const_get(the_class)

class_obj.singleton_class.class_eval do
define_method(meth_name) do
x + y
end
end
end


class Dog
end

SUM_TEMPLATE('Dog', 'FivePlusFour', 5, 4)
puts Dog.FivePlusFour

--output:--
9
 
7

7stud --

Robert K. wrote in post #999483:
def SUM_TEMPLATE(name, x, y)
class <<self;self;end.class_eval "def #{name}; #{x} + #{y}; end"
end

I'm curious how you expected the op to use that method?
 
R

Robert Klemme

Robert K. wrote in post #999483:

I'm curious how you expected the op to use that method?

I am not sure I get your point. The method was intended to be used as a
top level method similarly to how he formulated his C macro sample.

$ ruby19 <<CODE
def SUM_TEMPLATE(name, x, y)
class <<self;self;end.class_eval "def #{name}; #{x} + #{y}; end"
end
SUM_TEMPLATE("f",1,2)
p f
CODE
3

But, frankly, this use case seems to be so far away from his real use
case that this approach is likely not what is needed. Since I have no
further information I stick with the example and leave speculation about
the nature of the real issue aside.

Kind regards

robert
 
T

Thomas Preymesser

[Note: parts of this message were removed to make it a legal post.]

I'm looking for a construction in Ruby to generate a (big) set of
functions. I need these functions to be class methods and called by
unique names since they are called via an external API.

another way could be the method_missing method.
In my gem 'weekday' you can call methods like first_monday(yyyy,mm) ,
third_tuesday(...), last_sunday(...) and any other combination of a number
and weekday.

-Thomas
 
B

Brian Candler

Have a look at how ActionView (from Rails) caches its compiled
templates. It does this by defining methods on a module.

[actionpack/lib/action_view/template.rb]

def compile(view, mod) #:nodoc:
...
code = arity.abs == 1 ? @handler.call(self) :
@handler.call(self, view)

source = <<-end_src
def #{method_name}(local_assigns)
_old_output_buffer = @output_buffer;#{locals_code};#{code}
ensure
@output_buffer = _old_output_buffer
end
end_src
...
begin
mod.module_eval(source, identifier, 0)
ObjectSpace.define_finalizer(self, Finalizer[method_name,
mod])
rescue Exception => e # errors from template code
if logger = (view && view.logger)
logger.debug "ERROR: compiling #{method_name} RAISED #{e}"
logger.debug "Function body: #{source}"
logger.debug "Backtrace: #{e.backtrace.join("\n")}"
end
 
7

7stud --

Robert K. wrote in post #999582:
I am not sure I get your point. The method was intended to be used as a
top level method similarly to how he formulated his C macro sample.

$ ruby19 <<CODE
3

Oh, okay. But that isn't a class method.
 
R

Robert Klemme

Robert K. wrote in post #999582:

Oh, okay. =A0But that isn't a class method.

That's true. But OP mentioned class methods in just one sentence but
all his examples (Ruby and C) were not using class methods. Besides
it should not be too difficult to modify the code to generate class
methods. I still would like to see more realistic requirements as all
speculations about proper ways to solve the problem are moot as long
as we do not know the problem.

Kind regards

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
A

Andreas Lundgren

Hi, and thank you all!

Yes, the example is far from reallity in one sense, but your example
really did the trick and conceptually it's the same!

So, for the interessted reader, I will provide my code here. (I have a
similar method for setting values.) Via an Ole32 server i communicate
with a sw that can extract all types of data from a odd type of
database.

(My final dilemma is how to get the value of STUB_CALLS into
Templates.rb by defining it in my test program Impl.rb. To define it
direcrly in the template file in not very nice... And of cause I don't
want to have to define it at all when running in non-test-mode.)

______Templates.rb______
STUB_CALLS = 1; # If this is '1', all instruments are stubed for
debugging purpouse

def GET_VALUE(f_name, method, return_format, no_of_params)
# Make a parameter list and an argument list
if 0<no_of_params
arg_list = 'x1';
params = 'x1.to_s';
for i in 2..no_of_params do
arg_list = arg_list + ', x' + i.to_s;
params = params + ' + \', \' + x' + i.to_s + '.to_s';
end
params = params+';';
else
arg_list = '';
params = 0;
end

if (0==STUB_CALLS)
class <<self;self;end.class_eval "def #{f_name}(#{arg_list});
handle = OA.instance.getWIN32OLEHandle();
params = #{params};
handle.call(['MethodNameIn','Params'],[#{method},params]);
ret_val = handle.GetControlValue(#{return_format});
error = handle.GetControlValue('error out');
if OA.instance.checkForError(error);
return 'NaN';
else;
return ret_val;
end;
end"
else
class <<self;self;end.class_eval "def #{f_name}(#{arg_list});
params = #{params};
puts('Calling #{method}(#{arg_list}) with parameters <' + params
+ '>');
end"
end
end



______Templates.rb______ <- this is just three out of many
methods...
require '.\Difinitions.rb'
#***ParamsList ["database"]
GET_VALUE('customer_getFrequency', 'getcustomerfreq', 'ResultDbl', 1)

#***ParamsList ["database"]
GET_VALUE('customer_getSpend_thr', 'getcustspend', 'ResultDbl', 1)

#***ParamsList ["database", "start", "stop", "inc"]
GET_VALUE('shop_GetFundReq', 'getfundreq', 'ResultDbl', 4)



______Impl.rb______ <- The implementation does already exist, and it's
huge. This is just a few lines from my test program:
require '.\Difinitions.rb'
f = customer_getFrequency(cdb)
s = customer_getSpend_thr(cdb)
fr = shop_GetFundReq(sdb, 20110101, 20110201, 'all')
 
7

7stud --

1) Strings are mutable in ruby, so get rid of all those +'s.

2) Don't use back slashes in strings--unless you specifically intend to
use an escape character.

3)
My final dilemma is how to get the value of STUB_CALLS into
Templates.rb by defining it in my test program Impl.rb.

Why can't your methods have a 'debug' parameter with a default value?
Then for debugging, you can 'override' the default.
 
7

7stud --

Andreas Lundgren wrote in post #1000369:
5) Most ruby programmers don't use for-in because that construct just
calls each(), so ruby programmers call each() directly:

2..no_of_params.each do |i|
arg_list = arg_list + ', x' + i.to_s;
params = params + ' + \', \' + x' + i.to_s + '.to_s';
end
 
7

7stud --

7) If you will have less than 9 parameters, you can do this:

param_str = temp = 'x1'

10.times do |i|
temp = temp.succ
param_str << ', ' << temp
end

p param_str

--output:--
"x1, x2, x3, x4, x5, x6, x7, x8, x9, y0, y1"

But in ruby, you can define variadic methods:

def my_meth(*args)
args.each {|arg| p arg}
end

my_meth(1, 2, 3)
my_meth('a', 'b', 4)

--output:--
1
2
3
"a"
"b"
4
 
7

7stud --

10)

params = 'hello'
params = #{params};

--output:--
prog.rb:3: syntax error, unexpected $end


11) Here's some advice: buy a beginning ruby book and read it.
 
A

Andreas Lundgren

Hi!

This is why I did not put the real example at first, because then some
people that do not understand the code always brings these stupid
comments. I do not have any compilation errors, and the code works
perfectly, so I don't think that I need a beginning ruby book for that
reason. :) (First params does not contain a simple string; please
note the escaped string characters in the code that generates params.
Kind of strings within strings).

I cannot change the function signature since I cannot change the upper
layer that makes calls to my Ruby glue layer... That is why I would
like to introduce debugging by making changes in the glue layer at
"compile" time. (In C I would have used a #define for the pre
processor.)

"1) Strings are mutable in ruby, so get rid of all those +'s." - I'm
not sure that I understand this but it sounds interesting, what does
it mean?

Thanks for input about creating loops, new language = new flavours,
and that is nice to adapt to! :)

I think that I could make use of the variable parameter input, with
the extended error control code making sure all parameters are in
place, I think that this will be the same amount of code. But I
learned something new, so thanks!

BR,
Andreas
 
A

Andreas Lundgren

My bad to use the name "param" for two different content, but any
ways:

First params typically assembles to this:
x1.to_s + ', ' + x2.to_s + ', ' + x3.to_s + ', ' + x4.to_s
Now I have created the code, not the string.

The line
params_2 = #{params};
extends to:
params_2 = x1.to_s + ', ' + x2.to_s + ', ' + x3.to_s + ', ' + x4.to_s
and assembles my input so that
params_2 == 'myDB, 20110101, 20110201, all'

Output from the function when STUB_CALLS=1:
Calling shop_GetFundReq(x1, x2, x3, x4) with parameters <myDB,
20110101, 20110201, all>

BR,
Andreas
 
B

Brian Candler

Andreas Lundgren wrote in post #1000537:
The line
params_2 = #{params};
extends to:
params_2 = x1.to_s + ', ' + x2.to_s + ', ' + x3.to_s + ', ' + x4.to_s

Or you could do it like this:

params_2 = "#{x1}, #{x2}, #{x3}, #{x4}"

#{} interpolates the value of an expression into a string and calls to_s
automatically.

A slightly more DRY version:

params_2 = [x1, x2, x3, x4].join(", ")

Again, to_s is called automatically in this case.

Finally, if you used the *args syntax to collect a variable number of
args into an array, then it would become

params_2 = args.join(", ")

or even just: handle.call(['MethodNameIn','Params'],[method]+args)
Output from the function when STUB_CALLS=1:

Tiny suggestion: STUB_CALLS = false / true would be clearer, because
then you could say

do_something if STUB_CALLS

rather than

do_something if STUB_CALLS != 0

A global variable ($stub_calls) would arguably be preferable, since you
can change it at runtime without generating a warning. And better again
would be an instance variable of a class or module, because then it
lives in its own namespace.

module MyStuff
@stub_calls = false # set the default
def self.stub_calls
@stub_calls
end
def self.stub_calls=(x)
@stub_calls = x
end
end

puts MyStuff.stub_calls
MyStuff.stub_calls = true
puts MyStuff.stub_calls

Another thing to look at is using heredoc, as it means you don't have to
worry about quoting double-quotes:

class <<self;self;end.class_eval <<EOS
def #{f_name}(#{arg_list})
puts "Hello world"
end
EOS

Anyway, these are just tiny style suggestions. Having working code is
worth far more :)
 
R

Robert Klemme

My bad to use the name "param" for two different content, but any
ways:

First params typically assembles to this:
x1.to_s + ', ' + x2.to_s + ', ' + x3.to_s + ', ' + x4.to_s

That does not look good. If you have to encode numbers in variable
names, you probably rather want to use an Array for that or even
generate that directly:

params = no_of_params.times.map {|i| "x#{i}"}.join(', ')
Now I have created the code, not the string.

The line
params_2 = #{params};
extends to:
params_2 = x1.to_s + ', ' + x2.to_s + ', ' + x3.to_s + ', ' + x4.to_s
and assembles my input so that
params_2 == 'myDB, 20110101, 20110201, all'

When looking at your posting from 2:55 you don't really need "params =
..." in the generated code.
Output from the function when STUB_CALLS=1:
Calling shop_GetFundReq(x1, x2, x3, x4) with parameters <myDB,
20110101, 20110201, all>

SUB_CALLS should be a boolean and treated as such.

I can see why 7stud wrote his recommendation: the code looks overly
complicated. Also, there are some issues, for example:

handle.call(['MethodNameIn','Params'],[#{method},params]);

This only works if you call that method with something like

(..., "\"myMethod\"", ...)

where you should really passing the name plainly as string. Makes
usage much simpler and more robust. So in the method body you then
had

handle.call(['MethodNameIn','Params'],['#{method}', params]);

Same reasoning applies in other locations such as return_format.

And if you get rid of the no_of_params you can simply use *args and do
not have to go through the hassle generating argument lists. If may
be though that you want to be able to query arity from the method and
have it fail fast if the argument list does not match in which case
it's OK to generate the argument list.

All in all it seems this is simpler and more redundancy free:

def GET_VALUE(f_name, method, return_format, no_of_params)
args = no_of_params.times.map {|i| "x#{i}"}.join(", ")

body = if STUB_CALLS
%Q{printf("Calling #{method}(#{args}) with parameters <%p>\\n", [#{args}])}
else
%Q{
oai = OA.instance
handle = oai.getWIN32OLEHandle()
handle.call(['MethodNameIn','Params'],['#{method}', [#{args}]]);
ret_val = handle.GetControlValue(#{return_format});
error = handle.GetControlValue('error out');
return oai.checkForError(error) ? 'NaN': ret_val
}
end

code = "def #{f_name}(#{args})\n#{body}\nend"
puts code if ENV['DEBUG'] == 'true'
class <<self;self;end.class_eval code
end

Cheers

robert
 
A

Andreas Lundgren

First params typically assembles to this:
That does not look good.  If you have to encode numbers in variable
names, you probably rather want to use an Array for that or even
generate that directly:

params = no_of_params.times.map {|i| "x#{i}"}.join(', ')

your example generates code looks like this:
params = x1, x2, x3, x4
But with that I get an error (eval):3:in `+': can't convert Fixnum
into String (TypeError)

If it should work, I recon that it has to look like this:
params = "\"\#{" + no_of_params.times.map {|i| "x#{i}"}.join("}, \#{")
+ "}\""

becaus I need the generated code to look like this:
params = "#{x1}, #{x2}, #{x3}, #{x4}"

handle.call(['MethodNameIn','Params'],[#{method},params]);
Yes, this was a bug...

params_2 = "#{x1}, #{x2}, #{x3}, #{x4}"
Yes, I could use this, but still I need to assembly this line since I
dont know the number of input parameters at coding time. And I know
that no input parameters contain any expressions that needs to be
evaluated since it is an input from my generated function.



Join and times.map was kind of cool, my prototype has now replaced the
first loops with this:
arg_list = no_of_params.times.map {|i| "x#{i}"}.join(", ")
args = no_of_params.times.map {|i| "x#{i}.to_s"}.join("+','+")

(Using #{} instead of .to_s is basically the same:
args = "\"\#{" + no_of_params.times.map {|i| "x#{i}"}.join("}, \#{") +
"}\""

BR,
Andreas
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,901
Latest member
Noble71S45

Latest Threads

Top