Forum: Ruby pointer to function

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.
D778e297ab1545cd2e5714c28827cdab?d=identicon&s=25 ekkehard.horner (Guest)
on 2007-03-25 14:11
(Received via mailing list)
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
C2253345d045285df6751f7d65ff8569?d=identicon&s=25 Paul Stickney (Guest)
on 2007-03-25 14:47
(Received via mailing list)
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
753dcb78b3a3651127665da4bed3c782?d=identicon&s=25 Brian Candler (Guest)
on 2007-03-25 14:49
(Received via mailing list)
On Sun, Mar 25, 2007 at 09:10:12PM +0900, ekkehard.horner wrote:
>     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.
D778e297ab1545cd2e5714c28827cdab?d=identicon&s=25 ekkehard.horner (Guest)
on 2007-03-25 18:40
(Received via mailing list)
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
753dcb78b3a3651127665da4bed3c782?d=identicon&s=25 Brian Candler (Guest)
on 2007-03-25 20:12
(Received via mailing list)
On Mon, Mar 26, 2007 at 01:40:05AM +0900, ekkehard.horner wrote:
>       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.
D778e297ab1545cd2e5714c28827cdab?d=identicon&s=25 ekkehard.horner (Guest)
on 2007-03-25 21:26
(Received via mailing list)
Brian Candler schrieb:
> On Mon, Mar 26, 2007 at 01:40:05AM +0900, ekkehard.horner wrote:
[...]
>>     # 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
This topic is locked and can not be replied to.