Ruby: Symbol#to_proc Is a Lambadass

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
irb > [Object, Kernel, Class].map {|cls| cls.name }
=> ["Object", "Kernel", "Class"]

it can be written as

1
2
irb > [Object, Kernel, Class].map(&:name)
=> ["Object", "Kernel", "Class"]

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
class Symbol
  def to_proc
    Proc.new {|obj, *args| obj.send(self, *args) }
  end
end

After fixing cases with arrays and possibly monkey-patched send method you will end up with something like this:

1
2
3
4
5
class Symbol
  def to_proc
    Proc.new {|*args| args.shift.__send__(self, *args) }
  end
end

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:

Proc
1
2
irb > Proc.new {}
=> #<Proc:0x007f944a090c50@(irb):1>
Lambda
1
2
irb > lambda {}
=> #<Proc:0x007f944a08ac38@(irb):2 (lambda)>

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
irb > x = 1

irb > def z(x)
irb >   lambda { x }
irb > end

irb > lambda { x }.call
=> 1

irb > z(2).call
=> 2

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
irb > z = 1

irb > def x
irb >   lambda { z }
irb > end

irb > x.call
NameError: undefined local variable or method `z' for main:Object

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
irb > lambda &:name
=> #<Proc:0x007fcfa305dca8>
irb > (lambda &:name).call Class
=> "Class"

And it works! It does exactly what we’re expecting! So it looks like it is identical to

1
2
3
4
irb > lambda {|x| x.name }
=> #<Proc:0x007fcfa30465a8@(irb):8 (lambda)>
irb > lambda {|x| x.name }.call Class
=> "Class"

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
irb > lambda {}.binding
=> #<Binding:0x007fcfa30363b0>

One thing we can do with Binding object is to evaluate any binding captured by block:

1
2
3
irb > x = 1
irb > eval('x', lambda {}.binding)
=> 1

or

1
2
3
irb > x = 1
irb > lambda {}.binding.eval 'x'
=> 1

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
irb > lambda {}.binding
=> #<Binding:0x007fcfa40ab808>
irb > lambda {}.binding.eval 'y'
NameError: undefined local variable or method `y' for main:Object

Meet the Lambadass

Now let’s try to get the binding object of the proc created using Symbol#to_proc:

1
2
irb > (lambda &:name).binding
ArgumentError: Can't create Binding from C level Proc

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
rubinius > x = 1
rubinius > lambda {}.binding.eval 'x'
=> 1
rubinius > (lambda &:name).binding.eval 'x'
NameError: undefined local variable or method `x' on name:Symbol.

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:

symbol19.rb link
1
2
3
4
5
6
7
def to_proc
  sym = self
  Proc.new do |*args, &b|
    raise ArgumentError, "no receiver given" if args.empty?
    args.shift.__send__(sym, *args, &b)
  end
end

It looks very similar to what we initially defined. So what’s the problem? Let’s look at the error message again:

1
NameError: undefined local variable or method `x' on name:Symbol.

Of course there is no variable `x’ on the symbol :name! The key to understand it is that

1
lambda {}

is defined just here, where {} are, but

1
lambda &:name

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
rubinius > lambda {}.binding
=> #<Binding:0x179c @variables=#<Rubinius::VariableScope:0x17a0 module=Object method=#<Rubinius::CompiledCode irb_binding file=(irb)>> @compiled_code=#<Rubinius::CompiledCode __block__ file=(irb)> @proc_environment=#<Rubinius::BlockEnvironment:0x17a4 scope=#<Rubinius::VariableScope:0x17a0 module=Object method=#<Rubinius::CompiledCode irb_binding file=(irb)>> top_scope=#<Rubinius::VariableScope:0x1484 module=Object method=#<Rubinius::CompiledCode irb_binding file=.../rubinius/lib/19/irb/workspace.rb>> module=Object compiled_code=#<Rubinius::CompiledCode __block__ file=(irb)> constant_scope=#<Rubinius::ConstantScope:0x17a8 parent=nil module=Object>> @constant_scope=#<Rubinius::ConstantScope:0x17a8 parent=nil module=Object> @self=main>
rubinius > (lambda &:name).binding
=> #<Binding:0x17e0 @variables=#<Rubinius::VariableScope:0x17e4 module=Symbol method=#<Rubinius::CompiledCode to_proc file=kernel/common/symbol19.rb>> @compiled_code=#<Rubinius::CompiledCode to_proc file=kernel/common/symbol19.rb> @proc_environment=#<Rubinius::BlockEnvironment:0x17e8 scope=#<Rubinius::VariableScope:0x17e4 module=Symbol method=#<Rubinius::CompiledCode to_proc file=kernel/common/symbol19.rb>> top_scope=#<Rubinius::VariableScope:0x17e4 module=Symbol method=#<Rubinius::CompiledCode to_proc file=kernel/common/symbol19.rb>> module=Symbol compiled_code=#<Rubinius::CompiledCode to_proc file=kernel/common/symbol19.rb> constant_scope=#<Rubinius::ConstantScope:0x14cc parent=#<Rubinius::ConstantScope:0x14d0 parent=nil module=Object> module=Symbol>> @constant_scope=#<Rubinius::ConstantScope:0x14cc parent=#<Rubinius::ConstantScope:0x14d0 parent=nil module=Object> module=Symbol> @self=:name>

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
rubinius > (lambda &:name).binding
=> #<Binding:0x17e0 ... module=Symbol ...
rubinius > lambda { lambda &:name }.binding
=> #<Binding:0x1854 ... module=Object ...
rubinius > lambda { lambda &:name }.call.binding
=> #<Binding:0x1898 ... module=Symbol ...

JRuby

1
2
3
4
5
jruby > x = 1
jruby > lambda {}.binding.eval 'x'
=> 1
jruby > (lambda &:name).binding.eval 'x'
=> 1

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.