Brian's Waste of Time

Sun, 15 May 2005

Closures: What's the big deal?

Folks get all excited about closures for some reason. If you break away from the closure == anonymous function school, the cool thing about closures is that they let a function have state! To demonstrate, I'll borrow a page from Mr. Dybvig and implement a stack as a function which returns a function, which is passed messages explicitely:

def make_stack(list=[])
  lambda do |op, *args|
    case 
      when op == :push
        list.push args[0]
      when op == :pop
        list.pop
      else
        raise "Illegal Operation"
    end
  end
end

This snippet defines a function which returns an anonymous function which represents a classical stack data structure. It works:

s = make_stack

s.call :push, "First In"
s.call :push, "First Out"
puts s.call :pop
puts s.call :pop

~>
First Out
First In

The reason this works is because a new list local variable in the make_stack function is created each time make_stack is invoked, and that list is referenced by the Proc returned (created via the lambda call).

This, btw, is easily the basis for a (very) small object system:

def make_instance
  vars = {}
  lambda do |op, *args|
    if op == :define
      vars[args[0]] = args[1]
    else
      vars[op].call args
    end
  end
end

Okay, this technically cheats a bit because I needed Object to respond to :call, so I added

class Object  

  # "calling" an object returns itself unless subclass (Proc) overrides
  def call(*args)
    self
  end
end

Now, this is a complete toy, and hack, and is atrocious, but it illustrates the idea, which we then use:

b = make_instance
e = make_instance
b.call :define, :talk, lambda { puts "Hello, world"}
e.call :define, :talk, lambda { puts "Goodbye, world"}

b.call :talk
e.call :talk

b.call :define, :name, "Brian"       
puts( b.call :name )

~>
Hello, world
Goodbye, world
Brian

But it gets more fun, we want inheritance, right? Let's make a minor change to make_instance so that it supports prototype based inheritance, and add a new special funtion, new_instance

def make_instance(vars={})
  lambda do |op, *args|
    if op == :define
      vars[args[0]] = args[1]
    elsif op == :new_instance
      make_instance(vars.clone)
    else
      vars[op].call args
    end
  end
end

Now we can build up a "base class" and create new instances which inherit what is defined on the base (like JavaScript):

person = make_instance
person.call :define, :talk, lambda { |message| puts message }

k = person.call :new_instance
k.call :talk, "hello, world!"

Yea closures!

0 writebacks [/src/ruby] permanent link