Forum: Ruby Function as Parameter?

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.
Tomas N. (Guest)
on 2009-04-04 23:03
I am new to Ruby so I don't know a great deal about the language, but
what I am trying to do is the following:
Create an application that will display a login form. The user enters
their details and then that form disappears and they are taken to a
different form containing a canvas (which will have images drawn on it
eventually).

I don't want to create both forms and set their visibility, as the
invisible form will still be loaded in memory which is a waste. They I
thought of getting around this is to start the program in a "form
manager" style class, which then has the ability to create either of the
two forms. The trouble I am facing then, is that the button the form has
to pass back the username and password to the form manager, so that when
the Login form closes it may construct a Canvas from and pass the
details.

To make this happen I figured I would pass in a callback. Searching
around, I couldn't find a lot of info about callbacks, but I was reading
about passing a symbol. I may have got this wrong, but it looks like if
I pass in :function_name then it will be a symbol that I can call.

Well I tried it like this:

def TestFunc()
  puts 'Test'
end

xaApp = FXApp.new
LoginForm.new(xaApp,'TestApp',:TestCallback)
xaApp.create
xaApp.run

In my "Form Manager", and in my LoginForm's initialize I have:
def initialize(anApp,sTitle,aCallback)
    super(anApp, sTitle,:opts => DECOR_ALL, :width => 165, :height =>
250)

    FXLabel.new(self,"Username:", nil, JUSTIFY_LEFT|LAYOUT_FILL_X)
    FXTextField.new(self, 23, nil, 0, TEXTFIELD_NORMAL)

    FXLabel.new(self,"Password:", nil, JUSTIFY_LEFT|LAYOUT_FILL_X)
    FXTextField.new(self, 23, nil, 0, TEXTFIELD_NORMAL)

    xbButton = FXButton.new(self,"Log in",:opts =>
FRAME_RAISED|LAYOUT_CENTER_X|LAYOUT_FIX_WIDTH|LAYOUT_FIX_HEIGHT,:width
=> 80, :height => 20)
    xbButton.connect(SEL_COMMAND,aCallback)
end

This runs okay until I click the button, at which point I get the error:
C:\Users\Tomas\Desktop\Ruby>ruby testapp.rb
c:/ruby/lib/ruby/gems/1.8/gems/fxruby-1.6.12-mswin32/lib/fox16/responder2.rb:55:
in `onHandleMsg': undefined method `call' for :TestCallback:Symbol
(NoMethodErro
r)
        from testapp.rb:14:in `run'
        from testapp.rb:14

If I can get this working, then I will be able to move on to trying to
get it working with passing the two string parameters in as well. Can
someone please explain to me how I can call the function in my main code
when the button is clicked in the form?
Tomas N. (Guest)
on 2009-04-04 23:12
Tomas Na wrote:
> C:\Users\Tomas\Desktop\Ruby>ruby testapp.rb
> c:/ruby/lib/ruby/gems/1.8/gems/fxruby-1.6.12-mswin32/lib/fox16/responder2.rb:55:
> in `onHandleMsg': undefined method `call' for :TestCallback:Symbol
> (NoMethodErro
> r)
>         from testapp.rb:14:in `run'
>         from testapp.rb:14
>

Sorry, the error is:
c:/ruby/lib/ruby/gems/1.8/gems/fxruby-1.6.12-mswin32/lib/fox16/responder2.rb:55:
in `onHandleMsg': undefined method `call' for :TestFunc:Symbol
(NoMethodError)
        from testapp.rb:14:in `run'
        from testapp.rb:14

The top one says :TestCallback because that is what it was called
originally but I changed the name when I posted it here to make it a bit
clearer, forgot to rerun to get the error with proper function name.
David M. (Guest)
on 2009-04-05 19:45
(Received via mailing list)
On Saturday 04 April 2009 14:02:45 Tomas Na wrote:
> I am new to Ruby so I don't know a great deal about the language, but
> what I am trying to do is the following:
[snip]
> To make this happen I figured I would pass in a callback. Searching
> around, I couldn't find a lot of info about callbacks,

Search for blocks.

Your example would probably be written something like this:

LoginForm.new(xaApp,'TestApp') do |arg1, arg2|
  callback logic goes here
end

You may end up just calling a function inside that block, and that's
fine. But
the idiomatic way is likely to provide a block. If you need to pass more
than
one such "callback", you can pass proc objects, but that's often slower
and
definitely more annoying for whoever uses that class.
Igor P. (Guest)
on 2009-04-05 22:11
Tomas Na wrote:

> To make this happen I figured I would pass in a callback.
> Searching around, I couldn't find a lot of info about
> callbacks, but I was reading about passing a symbol. I may
> have got this wrong, but it looks like if I pass in
> :function_name then it will be a symbol that I can call.

Symbol is not really what you think. It is merely a special code/name
for "defined data" items (classes, variables and  methods (or functions
as you refer to them)) which are stored in Ruby's symbol table. As such,
namely as names, symbols are just like strings, they only are much more
efficiently handled by Ruby than strings. To distinguish between a
defined ruby item and its symbol the colon (:) is used before the name.
Passing a symbol of a method (function) around is just like passing its
name in the string form.

In order to execute a command as a string or a symbol you can use the {{
method }} method.

  def my_test
    puts "in my_test function"
  end
  puts :my_test           # -> my_test
  method(:my_test).call   # -> in my_test function
  method("my_test").call  # -> in my_test function
  var = "scope of creation"
  test_fu_lambda = lambda { puts 'Test: %s' % [ var ] }
  test_fu_lambda.call     # -> Test: scope of creation

Another way to pass a symbol to an iterator as an executable block is by
preceding it with the ampersand (&), which effectively converts the
method represented by the symbol to a proc! In Ruby, executable code is
passed around in procs, lambdas and Method objects. There is no such
thing as a function (method) pointer in Ruby. However a subtle
differences between these objects of executable code exist, and you
should learn about them. Especially you should know about the
differences between procs and lambdas. The most important differences
are in the way these executable objects are called (invoked) and the way
their respective {{ return }} mechanisms behave. In general procs use {{
yield }} invocation semantics but lambdas use the method invocation
semantics. Hence, procs are associated with block behaviour, whereas
lambdas are closer to method (function) behaviour.

Yet another important thing to understand is that procs and lambdas are
closures, but method objects are not. Closures are important because
they encapsulate in them all variables from the  scope in which they are
defined, which remain intact even when a proc or a lambda is executed in
a totally different scope.

You can not expect to learn all you need to know about this from a
single post on a forum, however, for you to continue working on your
program it should suffice to know that you need to convert a method
(function) you wish to pass to your LoginForm object to a lambda, and in
it, that is in LoginForm object, invoke it with the {{ call }} method
the i.e.; something like {{ test_fu_lambda.call }} as you have seen in
the example above.

If {{ LoginForm }} is your own class you created, you need to change the
invocation of your test function so it utilizes the {{ call }} method as
shown above. Try the following:

  var = "I am a variable from the scope where test_fu_lambda was
created"
  test_fu_lambda = lambda { puts 'Test: %s' % [ var ] }
  LoginForm.new(xaApp,'TestApp', test_fu_lambda)

Alternatively you can create a Method object of any of your methods and
pass it in instead. However, in this case your Method object will not
hold on to the variables from the scope in which it is defined:

  def TestFunc()
    puts 'Test: %s' % [ var ]
  end
  ...
  var = "this time not seen in my_fu_obj"
  my_fu_obj = method(:TestFunc)
  LoginForm.new(xaApp,'TestApp', my_fu_obj)

In both cases you have to change the invocation of the executable
objects you are passing to your {{ LoginForm }} to utilize the {{ *.call
}} syntax.

Indeed, what David wrote is the Ruby way of doing things, but sometimes
converting methods or blocks to executable objects is more appropriate.
Igor P. (Guest)
on 2009-04-05 22:32
Sorry, the in the above  TestFunc {{ var }} is out of scope. I just
wanted to emphasize the fact that Method objects are not closures.

Following is the correction

  def TestFunc()
    puts 'Test'
  end
  ...
  var = "this time not seen in my_fu_obj"
  my_fu_obj = method(:TestFunc)
  LoginForm.new(xaApp,'TestApp', my_fu_obj)
This topic is locked and can not be replied to.