pointer to function

E

ekkehard.horner

Just starting to learn Ruby, I'd like to ask if somebody would be so
kind as to help me 'translate' this Perl proof of concept code:

package cApp;
# POC application class that can run named Fncs via 'pointer to function'

my %Fncs = ( 'frsFnc' => [ 'just tell "frsFnc"', \&frsFnc ]
, 'secFnc' => [ 'just tell "secFnc"', \&secFnc ]
);

sub new
{ my $class = shift;

my $self = { name => shift
, Fncs => \%Fncs
};
bless( $self, $class );
}

sub run
{ my $self = shift;
my $sFnc = shift;
if( exists( $self->{ Fncs }->{ $sFnc } ) )
{ &{$self->{ Fncs }->{ $sFnc }->[ 1 ]}( $self )
}
else
{ print "no '$sFnc' in: ", join( ' ', keys( %{$self->{ Fncs }} ) ), "\n";
}
}

sub frsFnc
{ my $self = shift;
print $self->{ name }, '::frsFnc', "\n";
}

sub secFnc
{ my $self = shift;
print $self->{ name }, '::secFnc', "\n";
}

package main;
# main: create instance of cApp, get Fnc name, run named Fnc

my $oApp = cApp->new( 'trivial' );
my $sFnc = $ARGV[ 0 ] || 'frsFnc';
$oApp->run( $sFnc );

to Ruby. That's what I have now:

class CAPP

@name = 'anonym'
@Fncs = nil

def to_s
'POC application class that can run named Fncs via "pointer to function"'
end

def initialize( name )
@name = name
@Fncs = { 'frsFnc' => [ 'just tell "frsFnc"', nil ] ,
'secFnc' => [ 'just tell "secFnc"', nil ]
}
end

def run( sFnc )
puts @name + '::run( "' + sFnc + '" )'
if @Fncs.has_key?( sFnc )
puts "can't call fnc by name yet"
else
puts 'no ' + sFnc + ' in: ' + @Fncs.keys.join( ' ' )
end
end

def frsFnc
puts @name + '::frsFnc'
end

def secFnc
puts @name + '::secFnc'
end

end # class CAPP

# main: create instance of cApp, get Fnc name, run named Fnc

oApp = CAPP.new( 'trivial' )
puts oApp.to_s

puts "That's easy:"
oApp.frsFnc()
oApp.secFnc()

sFnc = ARGV[ 0 ] || 'frsFnc'
puts "Now call " + sFnc + ', please:'

oApp.run( sFnc )

Is it possible to replace the "nil" in @Fncs and the
puts "can't call fnc by name yet"
in run() with suitable Ruby expressions/statements?

I looked at lamba, but I don't want to define the functions using
strings.

Thanks
 
P

Paul Stickney

I have no idea what all that's supposed to do.
In Ruby you have methods (and Procs), but focusing on the
methods--they are invoked by a message. Normally this is done
magically for you. my_obj.my_method sends "my_method" to "my_obj".

Play with it in IRB:

class Me
def name
"Paul"
end
end
m = Me.new
m.name # => "Paul"
m.send :name # => "Paul"

class LetSomeRun
def initialize
@runnable = %w/first second/
end
def first; puts "running first" end
def second; puts "running second" end
def run (m)
if @runnable.include? m
send m
else
raise "Can't run that!"
end
end
end

r = LetSomeRun.new
r.run "first"
r.run "oops" # oops :(

You could also take a slightly different approach using Proc objects;
lambda works similar to how `sub {}` works in Perl.
x = lambda {|name| puts "hello #{name}!"}
x.call "fred"

So...

class Runnable2
def initialize
@runnable = {
"first" => lambda {|this, *args| this.run "second", "hello!"},
"second" => lambda {|this, *args| puts args.first}
}
end
def run (m, *args)
@runnable.include? m and @runnable[m].call self, *args
end
end
Runnable2.new.run "first"

I'm not exactly sure if this answers your question but, enjoy.

Paul
 
B

Brian Candler

my %Fncs = ( 'frsFnc' => [ 'just tell "frsFnc"', \&frsFnc ]
, 'secFnc' => [ 'just tell "secFnc"', \&secFnc ]
);

You have several options, depending on how literally you want to translate
this.

# (1) this is a Method object which is bound to the current object. Use
# fncs['frsFnc'][1].call(args...)
# to invoke it

fncs = {
'frsFnc' => [ 'just tell "frsFnc"', method:)frsFnc) ],
}

# (2) this is a Symbol giving the *name* of a method. Use
# some_object.send:)method, args...)
# to invoke it on an arbitary object (which could be 'self')

fncs = {
'frsFnc' => [ 'just tell "frsFnc"', :frsFnc ],
}

# (Note: this means you may not need to build the fncs hash at all; just let
# the caller pass in a method name. This assumes you don't mind the caller
# being able to call *any* method on your object)

# (3) this is an anonymous function, like "sub { ... }" in Perl. Use
# fncs['frsFnc'][1].call(args...)
# to invoke it.

fncs = {
'frsFnc' => [ 'just tell "frsFnc"', proc { puts "hello" } ],
}

A fourth option is to put the callable methods in a Module. Use
Modname.instance_method:)method_name) to get an 'unbound method' object,
which is like a method but not bound to a specific object instance. You then
have to bind it to a particular object when you call it.

There are probably others :)

HTH,

Brian.
 
E

ekkehard.horner

With the great help from Paul and Brian I changed just one line of the run method
of my CAPP class:

def run( sFnc )
puts @name + '::run( "' + sFnc + '" )'
if @Fncs.has_key?( sFnc )
# use send to call method whose name is contained in sFnc
# todo_stack.push( "relation between string variable and symbol" )
send sFnc
else
puts 'no ' + sFnc + ' in: ' + @Fncs.keys.join( ' ' )
end
end

and got a script that allows me to call CAPP methods by name specified on the
command line, even though I cheated a little bit by using the name == key of
@Fncs hash instead of the second element of the contained array.

To make myself familiar with the solutions to the 'pointer to function' problem
provided by Paul and Brian, I added CAPP methods like:

# just to have something to call
def tell_it( sParm )
puts @name + '::tell_it( "' + sParm + '" ) called successfully.'
end

and - more important:

# obj.send( aSymbol [, args ]* ) -> anObject
# Invokes the method identified by aSymbol, passing it any arguments specified.
# You can use __send__ if the name send clashes with an existing method in obj.
# Paul: def run (m) ... send m ..... r.run "first"
# Brian: some_object.send:)method, args...)
# fncs = { 'frsFnc' => [ 'just tell "frsFnc"', :frsFnc ], }

def sendSym
methods = { 'sym0' => [ 'using :tell_it' , :tell_it ] ,
'str0' => [ 'using "tell_it"', "tell_it" ] ,
}
methods.each { |keyval| send keyval[ 1 ][ 1 ], keyval[ 1 ][ 0 ] }
end
# Does send 'symbolize' the string? todo: check
# http://www.troubleshooters.com/codecorn/ruby/symbols.htm

or:

# obj.method( aSymbol ) -> aMethod
# Looks up the named method as a receiver in obj, returning a Method object (or
# raising NameError). The Method object acts as a closure in obj's object instance,
# so instance variables and the value of self remain available

def useCall
methods = { '0' => [ 'using method( :tell_it )', method( :tell_it ) ]
}
methods.each { |keyval| keyval[ 1 ][ 1 ].call keyval[ 1 ][ 0 ] }
end
# I believe, that's the way to go

Now I can start to

(a) put an enhanced version of the CAPP class into a module

(b) write a script that will create a new Ruby file <fname.rb> (requiring/using
this class CAPP to come) in the current directory, if called like
ruby xplore.rb <fname> new
resp. will add a new skeleton for method <mname> and a suitable entry in
the @methods hash, if called like
ruby xplore.rb <fname> add <mname> 'short description of <mname>'

(c) explore basic data types, looping, database work, ... with Ruby

Thank you very much Paul and Brian. Your answers were exactly what I was
looking for - and yes I did enjoy this.

Ekkehard
 
B

Brian Candler

def sendSym
methods = { 'sym0' => [ 'using :tell_it' , :tell_it ] ,
'str0' => [ 'using "tell_it"', "tell_it" ] ,
}
methods.each { |keyval| send keyval[ 1 ][ 1 ], keyval[ 1 ][ 0 ] }
end
# Does send 'symbolize' the string? todo: check
# http://www.troubleshooters.com/codecorn/ruby/symbols.htm

Yes, send allows you to pass either a string or a symbol. The symbol is
actually used for method dispatch; if you pass a string then it will convert
it into a symbol first (you can do this explicitly using String#to_sym or
String#intern; I think they are just aliases)

Regards,

Brian.
 
E

ekkehard.horner

Brian said:
# Does send 'symbolize' the string? todo: check
[...]

Yes, send allows you to pass either a string or a symbol. The symbol is
actually used for method dispatch; if you pass a string then it will convert
it into a symbol first (you can do this explicitly using String#to_sym or
String#intern; I think they are just aliases)

Regards,

Brian.
Thanks Brian,

I added

def sendStr
methods = { 'str0' => [ 'using "tell_it"', "tell_it" ] ,
}
methods.each { |keyval|
send keyval[ 1 ][ 1 ].intern, keyval[ 1 ][ 0 ]
send keyval[ 1 ][ 1 ].to_sym, keyval[ 1 ][ 0 ]
}
end

and learned: When in doubt, use ri (String#to_sym wasn't mentioned
in "ProgrammingRuby.chm").

Regards

Ekkehard
 

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
474,239
Messages
2,571,195
Members
47,831
Latest member
ClaudiaSig

Latest Threads

Top