Skip to content

Objects Are Lambdas Are Objects

This is the story about how a simple question about some Ruby syntax led me to finally understanding a key concept in computer science.

The other day I was wondering why in Ruby you have to use the syntax foo.call to call a lambda. Why can’t you just call it like a regular function? Which - since you don’t need braces for a function without arguments in ruby - would simply look like this: foo. It got me thinking, what exactly is a lambda in Ruby anyway? Lambdas have three main properties that I can think of:

  1. They are anonymous
  2. They are first-class
  3. They capture their environment

In most languages, a lambda is a first-class function. But Ruby doesn’t even have functions, not really. Check this out:

def foo
  "bar"
end # => :foo

Object.private_instance_methods.include?(:foo) # => true
Object.new.send(:foo) # => "bar"

So a “function” definition is really just a private method on Object. Ruby doesn’t have functions, it has methods. When you “call” a method, really you’re sending a message to the object, and in return it gives you a response.

Now, objects are anonymous and first-class by default - you can pass them around, and you can assign them to variables. In Ruby, everything is an object, therefore everything is first-class by default. That’s a really nice consequence of the “everything is an object” model of computation. In-keeping with this model, lambda’s should also be objects. And if lambdas are objects, they would automatically satisfy two of the required properties. Well it turns out that that’s exactly what they are. Lambdas in Ruby are just objects with a call method. Check this out1:

foo = lambda { "bar" }

foo.class # => Proc
foo.methods.include?(:call) # => true
foo.send(:call) # => "bar"

So you might implement a simple lambda like this:

foo = Object.new
def foo.call
  "bar"
end

foo.call # => "bar"

But while this is first-class and anonymous, it still doesn’t capture it’s environment - the third property of lambdas. In Ruby you can capture the environment at a particular point in the code using the binding object, which is an instance of the Binding class. So if we store an instance of binding in our object at the point where it is created, the object will satisfy all the properties of a lambda! Thus a lambda could be implemented as an object like this2:

class MyLambda
  def initialize(binding, block)
    @binding = binding
    @block = block
  end

  def call
    @binding.eval(@block)
  end
end

Low and behold, it behaves just like a lambda:

foo = 1
MyLambda.new(binding, 'foo').call # => 1

my_lambda = MyLambda.new(binding, 'bar')
bar = 1
my_lambda.call # => NameError

baz = 1
my_lambda = MyLambda.new(binding, 'baz')
baz = 2
my_lambda.call # => 2

qux = 1
MyLambda.new(binding, 'qux = 2').call
qux # => 2

This is really neat! Ruby doesn’t need a special concept of first-class functions because objects are inherently first class, and you can represent a function as an object. A lambda is just a special case of this, which captures it’s environment as well3. So this answers our question of why you need to use foo.call to call a lambda in Ruby, and also shows how objects can be used to represent lambdas. Now let’s look at it from the other direction and work our way back.

If Ruby’s philosophy is “everything is an object”, Scheme’s could be “everything is a lambda” (along with a few axioms[pdf]). This model of computation is called lambda calculus. Given that lambdas capture their environment, we can construct objects using lambdas like this:

(define (make-foo x)
  (let ((x x))
    (lambda (msg . args)
      (cond ((eq? msg 'x) x)
            ((eq? msg 'set-x!) (set! x (car args)))))))

You can then send messages to the object like so:

(define foo (make-foo "bar"))
(foo 'x) ; => "bar"
(foo 'set-x! "baz")
(foo 'x) ; => "baz"

We can define let in terms of lambda as well, i.e. (let ((var1 exp1) (var2 exp2)) body) is equivalent to: ((lambda (var1 var2) body) exp1 exp2). And (define (foo x y) body) is just syntactic sugar for (define foo (lambda (x y) body)). So (and given that define, cond, eq?, set!, and car are primitives of the language), we have simulated objects using only lambdas.

So in Ruby, we have a model of computation where everything is an object, and we can use objects to simulate lambdas. In Scheme, we have a model of computation where everything is a lambda, and we can use lambdas to simulate objects! That’s a really elegant symmetry. Objects and lambdas can be explained in terms of each other, and neither is more fundamental than the other. Object Oriented and Functional programming are two sides of the same coin. This is captured nicely by the following saying: “Closures are a poor man’s objects, and Objects are a poor man’s closures”. Sure the implementation of objects in Scheme is a little bit ugly, and the implementation of lambdas in Ruby is a little bit ugly as well. But that’s what is meant by “poor”. And the ugliness is really just a result of the limitations of the languages in which I’ve chosen to implement them4 - some languages will be able to implement them more elegantly than others. The higher-level idea still stands.

I had understood for some time that you can simulate objects with lambdas. But it’s only now that I’ve thought about how lambdas work in Ruby that I’ve come to understand how lambdas can be simulated with objects. This is not a new idea by any means, but it was a fun journey getting to this point. And I feel like I have a much better understanding of the relationship between Functional and Object Oriented models of computation.

Further reading:

  1. In case you’re wondering why the class of a lambda is Proc, technically lambdas are implemented as Procs, which behave slightly differently from lambdas. Procs have a predicate function lambda? which can be used to distinguish between them. 

  2. Having to use eval here is kind of ugly, but it’s the only way I can think of to evaluate an expression in a given execution context. If we don’t care about the environment capture property, then implementing a lambda as an object is trivial. 

  3. The implementation of lambdas in Ruby does this by default, so you don’t have to explicitly pass in the binding instance each time you create one as we do here. 

  4. Of course, in Scheme, you can use macros to abstract away that ugliness. That is how most object systems in scheme are implemented. Maybe lambdas could be implemented more elegantly in Ruby using it’s metaprogramming features?