Insights into Ruby from Lua

posted 2006-Feb-11

In my job, I have to use Lua to program some of our software. For anything complex, I love OOP concepts. For those that don’t know, Lua doesn’t have OOP, though it has some syntactic sugar to make basic OOP possible.

So, I wrote a little OOP library based on Ruby’s object model. I created an Object “class”, and a Class “class”, and Class is an instance of an Object, and instances of Class are themselves objects that create instance that inherit from the class on up to the Object prototype. And so on. (Once I got my head around Lua concepts, it’s remarkable how easy it was. Only 99 lines of Lua code.)

In the process, I tried to make a OOP system that was better than Ruby’s, by changing two things that have always bothered me. In both cases, I failed; the reasons why I failed were both “Ah-HA!” moments for me that gave me insight (I think) into why Ruby is designed as it is. I share them with you below.

new versus initialize

I was always a little bothered by the fact that in Ruby when you call new on a class, you define an initialize function to handle it. I mean…wtf, why not just call what you write new instead? I was also annoyed that if you return a specific value from your initialize method it gets ignored.

I decided that I’d have the user actually write their own new function for the class, and use its return value. In implementation, it looked something like this:

Rectangle = AKClass:new( )
function Rectangle:new( height, width )
  local theInstance = AKObject:new( self )
  theInstance.height = height
  theInstance.width = width
  return theInstance
end

As I wrote code like that again and again, I realized that in every new function I had to call AKObject:new( self ) to create the instance, and then return it at the end. I realized that if every class inherited it’s new from AKObject, then I could just delegate that work to AKObject, and let the class just define what to do to the instance. Suddenly, I had Ruby again, and (now that I’d seen the alternative) I liked it:

function AKObject.new( owningClass, ... )
  local theInstance = {
    -- Lua stuff for setting up the inheritance here
  }

  if type( owningClass.initialize ) == 'function' then
     owningClass.initialize( theInstance, unpack( arg ) )
     -- No point in using the return value here
  end

  return theInstance
end

function Rectangle:initialize( height, width )
  self.height = height
  self.width = width
end

The Trouble with First-Class Functions

I have repeatedly complained on the ruby-talk mailing list about the fact that Methods aren’t first-class functions. (The term ‘first-class functions’ as I’m using it here is another way of saying ‘function literals’: it means that functions are just another atomic variable type, that can be invoked with any object as the ‘self’ scope.) Blocks and Procs and Methods are all different? Bleah! Special-cases are the opposite of elegant.

Lua is nothing but first-class functions, so I was happy…until I tried to write a super method to call the method with the same name on the parent object. Inside a function, I have no idea what that function is called. So I then tried to write a special-case (urgh!) superinit method specifically for calling parent initializer…and that failed. I finally got to the core of the problem this morning:

function Rectangle:initialize( width, height )
  self.width = width
  self.height = height
end

function Square:initialize( size )
  self.class.superclass.initialize( self, size, size )
end

function UnitSquare:initialize( )
  self.class.superclass.initialize( self, 1 )
end

(For those not familiar with Lua, the colon used when defining a function means “Hey, please create an implicit first parameter named self because I’m too lazy to type it each time.” This is why I can pass the self from the subclasses to the parent method, and have the initializer operate on it instead.)

The above works just fine when I create a new Square, but I get some infinite recursion when I try to create a new UnitSquare. Here’s an English trace of what’s happening:

  1. AKObject creates a new instance of UnitSquare, and passes that instance to the UnitSquare initializer.

  2. UnitSquare’s initializer finds the class of the supplied object (UnitSquare), its parent class (Square), and calls that class’s initialize function, passing along the UnitSquare instance.

  3. Square’s initializer finds the class of the supplied object (UnitSquare), it’s parent class (Square), and…oh hell, we’re stuck in a loop.

The problem is that when I wrote “self.class.superclass” what I really meant was “Hey, I want the superclass of the class that owns this method, not the object that I happen to be operating on.”

Which means that methods need to be associated with a class.

Which means that they’re not first-class functions.

Aw fuck. Matz is smart. :)

net.mind details other résumé contact
Phrogz.net