Kernel.lambda() not returning a lambda Proc in Ruby 1.9
-
11-12-2019 - |
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 Proc
s 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.
Solution
I would suggest reading this: http://ruby-doc.org/core-1.9.3/Proc.html#method-i-lambda-3F
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