Question

I'm writing a simple event bus to use in an application. This part works fine:

#!/usr/bin/env ruby

module EventBus
  @callbacks = {}

  module_function

  def on event, &callback
    @callbacks[event] ||= []
    @callbacks[event].push callback
  end

  def fire event, *data
    @callbacks[event].each do |callback|
      callback.call *data
    end
  end
end

EventBus.on :foo do |x| puts x end
EventBus.fire :foo, "test"
# => test

Because of the complex nature of my program, and the fact that Procs take arguments in a very loosey-goosey fashion, I want some argument checking on my events. lambda is the obvious choice for this:

EventBus.on :bar, &(lambda do |x| puts x end)

# Will raise an ArgumentError, since the event was fired without any arguments
# Remember that this is the desired behavior
EventBus.fire :bar

Clearly, the syntax of the on call is ugly due to the &(lambda do ... end). I'd rather be able to just use do ... end (i.e. just pass it a normal block without the unary ampersand operator) and convert that into a lambda. I tried the obvious:

...
  def on_lambda event, &callback
    @callbacks[event] ||= []
    @callbacks[event].push(lambda &callback)
    # check if the added callback is lambda. (spoiler alert: it isn't)
    puts @callbacks[event].last.lambda?
  end
end

EventBus.on_lambda :baz do |x| puts x end
# I would expect the callback to be a lambda, and thus throw an ArgumentError,
# but neither of these holds.
EventBus.fire :baz

As I understand it, on_lambda takes a block, converts it to a Proc referenced locally by callback (via the &). I'm pushing the result of calling lambda with callback converted back in to a block. I expect this to return a lambda derived from the callback Proc, so my question is: why is the element on the array a normal Proc.?

ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-linux]


Update: Prompted by megas's mention of { } I looked into the ->() { } syntax (I've seen it called the "stab" operator). It produces a lambda from the block specified. It's probably as close as I'll get to the mixture of normal block syntax and argument checking I wanted. All I have to do is tweak the on method:

def on event, callback
  @callbacks[event] ||= []
  @callbacks[event].push callback
end

and then attach callbacks like so:

EventBus.on :bam, ->(x) do
  puts x
end

I'm still curious as to the original question, however.

Was it helpful?

Solution

OTHER TIPS

Blocks passed to method calls are always packaged as Procs. If you want lambdas, you must construct them explicitly, by saying lambda {|...| ... }. Btw. I also like lambdas better than procs, but if I remeber well, Matz in his tutorial introduces procs as "more featureful" than lambdas (procs "have tricks" of adapting to variable number of arguments etc., lambdas don't).

You'd have to do something like this to pass lambdas in:

def on_lambda event, callback
  (@callbacks[event] ||=[]) << callback
  puts @callbacks[event].last.lambda?
end

EventBus.on_lambda :baz, lambda do |x| puts x end
EventBus.fire :baz
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top