It all started with adding to_proc
method to the Symbol
class. It works pretty simple and looks even better. Instead of writing
1 2 |
|
it can be written as
1 2 |
|
And what it does is just calling to_proc
method on the symbol :name
(which returns a Proc
) and converting the proc to a block with &
operator (because map
takes a block, not a proc).
The naive implementation of the Symbol#to_proc
would look like this:
1 2 3 4 5 |
|
After fixing cases with arrays and possibly monkey-patched send
method you will end up with something like this:
1 2 3 4 5 |
|
It’s all well understood and described over the Internet. To understand why Symbol#to_proc
is a lambadass (and what it means) let’s move on to the kinds of Ruby Procs.
Different kinds of Ruby Procs
As you know there are two kinds of Ruby Procs - procs and lambdas. They not only differ in how they check arity and treat return keyword, but also look different in irb:
1 2 |
|
1 2 |
|
You can see (lambda)
suffix displayed for lambdas only and something like context (irb):2
for both of them. It turns out that there is a third kind of procs which I call lambadass but let’s talk about lambda scope or context at first.
Scope
Procs and lambdas (which are objects of class Proc
too) are closures like blocks. The only thing which is important for us is that they are evaluated in the scope where they are defined or created.
It means that any block (or proc or lambda) includes a set of bindings (local variables, instance variables, etc.) captured at the moment when it was defined. Simple example demonstrating this in action:
1 2 3 4 5 6 7 8 9 10 11 |
|
As method definition is a scope gate the only known binding with name x
inside method z
is the method parameter. To visually grab the context of defined lambda you can consider {}
as constructor (not the lambda
word).
It also means that the lambda defined inside method body knows nothing about any bindings defined out of the method scope:
1 2 3 4 5 6 7 8 |
|
Despite the fact that the lambda was called on the top level it was defined in the method where binding with name z
didn’t exist. Once the scope or context is captured it remains the same inside the block no matter where it’s called from.
Lambadass
Lambadass is a proc or a lambda which looks similar to a normal proc or lambda but behaves differently.
So let’s back to the Symbol#to_proc
. Usually it is used instead of block in such a method like map
.
As to_proc
method returns a proc what if we want to use it standalone as any other proc? Let’s do just that:
1 2 3 4 |
|
And it works! It does exactly what we’re expecting! So it looks like it is identical to
1 2 3 4 |
|
Cool!
But wait a minute… Why does the returned Proc
object look different? #<Proc:0x007fcfa305dca8>
instead of #<Proc:0x007f944a090c50@(irb):1>
? It’s still a Proc
, it’s still a callable object but it’s missing something. Looking at the object representation I would say it’s missing a context. How can we check it?
Binding
All the bindings captured from the scope where a block is defined are stored in the Binding
object. We can get it by calling Proc#binding
method:
1 2 |
|
One thing we can do with Binding
object is to evaluate any binding captured by block:
1 2 3 |
|
or
1 2 3 |
|
It will raise an exception if binding with such a name is not defined but every block (or proc) has an associated binding object.
1 2 3 4 |
|
Meet the Lambadass
Now let’s try to get the binding object of the proc created using Symbol#to_proc
:
1 2 |
|
Obviously there is something wrong with it. It turns out that Symbol#to_proc
method is implemented in C in MRI (Matz’s Ruby Interpreter which is written in C). Of course it doesn’t make any sense to get the context of C level Proc object (would be nice though).
Let’s try another interpreters.
Rubinius
1 2 3 4 5 |
|
We got the exception again. But it says that binding with name x
is not defined. As Rubinius (at least Symbol#to_proc
) is written in Ruby itself let’s look at its implementation:
1 2 3 4 5 6 7 |
|
It looks very similar to what we initially defined. So what’s the problem? Let’s look at the error message again:
1
|
|
Of course there is no variable `x’ on the symbol :name
! The key to understand it is that
1
|
|
is defined just here, where {} are, but
1
|
|
is defined inside the Symbol
class in the to_proc
method which knows nothing about any bindings defined elsewhere Symbol
object. As a callable object it behaves correctly but the scope is absolutely different.
To better understand it let’s take a look at the Binding
object:
1 2 3 4 |
|
You can see that in the first case the module is Object
and compiled code is block in irb where in the second output the module is Symbol
and compiled code is to_proc
method in the file kernel/common/symbol19.rb
.
Of course if you wrap lambda &:name
in another lambda the scope of this top lambda will be Object because it is not defined in Symbol anymore. Anyway the scope of the inner lambda will remain unchanged:
1 2 3 4 5 6 |
|
JRuby
1 2 3 4 5 |
|
That’s what almost everybody I asked would expect. No errors, works identical. But if you remember self-written to_proc
method, how scope is defined in Ruby and Rubinius implementation this behaviour should be wrong even if it seems the only working without big surprises.
Epilogue
There is a Proc. Sometimes it can be a lambda. The same object with different behaviour. Different from just a proc but the same across the interpreters at least. They called it lambda. They even created a new syntax for it. With current implementation of Symbol#to_proc
we have third behaviour of Proc. Behaviour that differs across interpreters. I call it lambadass.