tags:

views:

1087

answers:

5

I'm not sure of the best idiom for C style call-backs in Ruby - or if there is something even better ( and less like C ). In C, I'd do something like:

void DoStuff( int parameter, CallbackPtr callback )
{
  // Do stuff
  ...
  // Notify we're done
  callback( status_code )
}

Whats a good Ruby equivalent? Essentially I want to call a passed in class method, when a certain condition is met within "DoStuff"

+20  A: 

Please see a Ruby Book and/or Tutorial, this "idiomatic block" is a very core part of everyday Ruby

The idiomatic way is to use a block:

def x(z)
  yield z
end
x(3) {|y| y*y}  # => 9

Or perhaps converted to a proc: (Only do this to save the block for later or in other special cases; here I show that the "block", converted to a Proc implicitly with &block, is just another value):

def x(z, &block)
  callback = block
  callback.call(z)
end

# look familiar?
x(4) {|y| y * y} # => 16

However, a lambda can be use just as easily (but this is not idiomatic):

def x(z,fn)
  fn.call(z)
end

# just use a lambda (closure)
x(5, lambda {|y| y * y}) # => 25

While the above approaches can all wrap "calling a method" as they create closures, bound methods can also be treated as first-class callable objects:

class A
  def b(z)
    z*z
  end
end

callable = A.new.method(:b)
callable.call(6) # => 36

# and since it's just a value...
def x(z,fn)
  fn.call(z)
end
x(7, callable) # => 49

In addition, sometimes it's useful to use the #send method (in particular if a method is known by name, here it saves an intermediate wrapping bound-method; ruby is a message-passing based OO system):

# Using A from previous
def x(z, a):
  a.__send__(:b, z)
end
x(8, A.new) # => 64
pst
+1  A: 

So, this may be very "un-ruby", and I am not a "professional" Ruby developer, so if you guys are going to smack be, be gentle please :)

Ruby has a built-int module called Observer. I have not found it easy to use, but to be fair I did not give it much of a chance. In my projects I have resorted to creating my own EventHandler type (yes, I use C# a lot). Here is the basic structure:

class EventHandler

  def initialize
    @client_map = {}
  end

  def add_listener(id, func)
    (@client_map[id.hash] ||= []) << func
  end

  def remove_listener(id)
    return @client_map.delete(id.hash)
  end

  def alert_listeners(*args)
    @client_map.each_value { |v| v.each { |func| func.call(*args) } }
  end

end

So, to use this I expose it as a readonly member of a class:

class Foo

  attr_reader :some_value_changed

  def initialize
    @some_value_changed = EventHandler.new
  end

end

Clients of the "Foo" class can subscribe to an event like this:

foo.some_value_changed.add_listener(self, lambda { some_func })


I am sure this is not idiomatic Ruby and I am just shoehorning my C# experience into a new language, but it has worked for me.

Ed Swangren
+3  A: 

The ruby equivalent, which isn't idiomatic, would be:

def my_callback(a, b, c, status_code)
  puts "did stuff with #{a}, #{b}, #{c} and got #{status_code}"
end

def do_stuff(a, b, c, callback)
  sum = a + b + c
  callback.call(a, b, c, sum)
end

def main
  a = 1
  b = 2
  c = 3
  do_stuff(a, b, c, method(:my_callback))
end

The idiomatic approach would be to pass a block instead of a reference to a method. One advantage a block has over a freestanding method is context - a block is a closure, so it can refer to variables from the scope in which it was declared. This cuts down on the number of parameters do_stuff needs to pass to the callback. For instance:

def do_stuff(a, b, c, &block)
  sum = a + b + c
  yield sum
end

def main
  a = 1
  b = 2
  c = 3
  do_stuff(a, b, c) { |status_code|
    puts "did stuff with #{a}, #{b}, #{c} and got #{status_code}"
  }
end
dbarker
Douglas
A: 

I often implement callbacks in Ruby like in the following example. It's very comfortable to use.

class Foo
   # Declare a callback.
   def initialize
     callback( :on_die_cast )
   end

   # Do some stuff.
   # The callback event :on_die_cast is triggered.
   # The variable "die" is passed to the callback block.
   def run
      while( true )
         die = 1 + rand( 6 )
         on_die_cast( die )
         sleep( die )
      end
   end

   # A method to define callback methods.
   # When the latter is called with a block, it's saved into a instance variable.
   # Else a saved code block is executed.
   def callback( *names )
      names.each do |name|
         eval <<-EOF
            @#{name} = false
            def #{name}( *args, &block )
               if( block )
                  @#{name} = block
               elsif( @#{name} )
                  @#{name}.call( *args )
               end
            end
         EOF
      end
   end
end

foo = Foo.new

# What should be done when the callback event is triggered?
foo.on_die_cast do |number|
   puts( number )
end

foo.run
henning
+2  A: 

Explored the topic a bit more and updated the code.

The following version is an attempt to generalize the technique, although remaining extremely simplified and incomplete.

I largely stole - hem, found inspiration in - the implementation of callbacks of DataMapper, which seems to me quite complete and beatiful.

I strongly suggest to have a look at the code @ http://github.com/datamapper/dm-core/blob/master/lib/dm-core/support/hook.rb

Anyway, trying to reproduce the functionality using the Observable module was quite engaging and instructive. A few notes:

  • method added seems to be require because the original instance methods are not available at the moment of registering the callbacks
  • the including class is made both observed and self-observer
  • the example is limited to the instance methods, does not support blocks, args and so on

code:

require 'observer'

module SuperSimpleCallbacks
  include Observable

  def self.included(klass)
    klass.extend ClassMethods
    klass.initialize_included_features
  end

  # the observed is made also observer
  def initialize
    add_observer(self)
  end

  # TODO: dry
  def update(method_name, callback_type) # hook for the observer
    case callback_type
    when :before then self.class.callbacks[:before][method_name.to_sym].each{|callback| send callback}
    when :after then self.class.callbacks[:after][method_name.to_sym].each{|callback| send callback}
    end
  end

  module ClassMethods
    def initialize_included_features
      @callbacks = Hash.new
      @callbacks[:before] = Hash.new{|h,k| h[k] = []}
      @callbacks[:after] = @callbacks[:before].clone
      class << self
        attr_accessor :callbacks
      end
    end

    def method_added(method)
      redefine_method(method) if is_a_callback?(method)
    end

    def is_a_callback?(method)
      registered_methods.include?(method)
    end

    def registered_methods
      callbacks.values.map(&:keys).flatten.uniq
    end

    def store_callbacks(type, method_name, *callback_methods)
      callbacks[type.to_sym][method_name.to_sym] += callback_methods.flatten.map(&:to_sym)
    end

    def before(original_method, *callbacks)
      store_callbacks(:before, original_method, *callbacks)
    end

    def after(original_method, *callbacks)
      store_callbacks(:after, original_method, *callbacks)
    end

    def objectify_and_remove_method(method)
      if method_defined?(method.to_sym)
        original = instance_method(method.to_sym)
        remove_method(method.to_sym)
        original
      else
        nil
      end
    end

    def redefine_method(original_method)
      original = objectify_and_remove_method(original_method)
      mod = Module.new
      mod.class_eval do
        define_method(original_method.to_sym) do
          changed; notify_observers(original_method, :before)
          original.bind(self).call if original
          changed; notify_observers(original_method, :after)
        end
      end
      include mod
    end
  end
end


class MyObservedHouse
  include SuperSimpleCallbacks

  before :party, [:walk_dinosaure, :prepare, :just_idle]
  after :party, [:just_idle, :keep_house, :walk_dinosaure]

  before :home_office, [:just_idle, :prepare, :just_idle]
  after :home_office, [:just_idle, :walk_dinosaure, :just_idle]

  before :second_level, [:party]

  def home_office
    puts "learning and working with ruby...".upcase
  end

  def party
    puts "having party...".upcase
  end

  def just_idle
    puts "...."
  end

  def prepare
    puts "preparing snacks..."
  end

  def keep_house
    puts "house keeping..."
  end

  def walk_dinosaure
    puts "walking the dinosaure..."
  end

  def second_level
    puts "second level..."
  end
end

MyObservedHouse.new.tap do |house|
  puts "-------------------------"
  puts "-- about calling party --"
  puts "-------------------------"

  house.party

  puts "-------------------------------"
  puts "-- about calling home_office --"
  puts "-------------------------------"

  house.home_office

  puts "--------------------------------"
  puts "-- about calling second_level --"
  puts "--------------------------------"

  house.second_level
end
# => ...
# -------------------------
# -- about calling party --
# -------------------------
# walking the dinosaure...
# preparing snacks...
# ....
# HAVING PARTY...
# ....
# house keeping...
# walking the dinosaure...
# -------------------------------
# -- about calling home_office --
# -------------------------------
# ....
# preparing snacks...
# ....
# LEARNING AND WORKING WITH RUBY...
# ....
# walking the dinosaure...
# ....
# --------------------------------
# -- about calling second_level --
# --------------------------------
# walking the dinosaure...
# preparing snacks...
# ....
# HAVING PARTY...
# ....
# house keeping...
# walking the dinosaure...
# second level...

This simple presentation of the use of Observable could be useful: http://www.oreillynet.com/ruby/blog/2006/01/ruby_design_patterns_observer.html

LucaB
This is a great resource, thank you.
gregf