Metacircularity

[RSS feed]
 

September 12 2005
Updated: September 17 2005

One of the great things about so-called dynamic languages (meaning the Python's, Converge's, and Ruby's of this world vs the static C's, Java's etc.) is the ability to rummage around the type system at run-time. In Converge, every object has a slot instance_of which tells you the class that any given object is an instance of. This makes sense because classes are normal run-time objects, which is rather different than some static languages like C++, where the concept of a class is essentially 'compiled out' and does not exist at run-time. In Converge, if one writes the following code:
Sys.println(1.instance_of)
then at run-time the following is printed:
<Class Int>
Since 1 is an integer, it's not too surprising that the class 1 is an instance of is the Int class. Rummaging around the run-time system in this fashion is known as reflection. Fans of dynamically typed languages often extol the virtues of reflection. There are an awful lot of interesting, fun things that one can do with reflection - and a few of them are even useful.

Whilst the examples above show a common useage of reflection, a question that ultimately occurs to those who make heavy use of reflection is what happens if one goes further? In other words, whilst one is not surprised to see that the instance_of slot of 1 returns the Int class, what happens if one does the same thing to the Int class itself? In Converge the following code:

Sys.println(1.instance_of.instance_of)
prints the following at run-time:
<Class Class>
in other words the Int class is an instance of the class called Class (I apologize now to those readers who are reading this without having access to both monospaced / non-monospaced fonts - the rest of this article relies on font changes to be readable). Again, perhaps this is not too surprising. But what is the class called Class an instance of? In Converge the following code:
Sys.println(1.instance_of.instance_of.instance_of)
prints the following at run-time:
<Class Class>
This generally surprises people, but given the preceding example it makes perfect sense. The class called Class is an instance of the class called Class.

What's confusing about this last example is that we have spotted that the Converge type / class system has a 'cycle' in it. When, in the beginning of this article, we looked at the contents of a instance_of slot, we came across a new class at each point; however the Class class points to itself.

The cycle that we observe in the Converge type system is the point of metacircularity. What actually happens when one traverses the instance_of slot is that one crosses a meta-level. Anything with the meta- prefix tends to make peoples head explode; meta-classes exist in several languages, but are understood by few people. My point in this entry isn't really to attempt to explain meta-whatnot's - far better people than me have tried that before. I just want to point out a few things that I think are pertinent and conclude with a minimal runnable example.

One very interesting thing is that in a system such as Converge there are a minimum number of meta-levels, but no fixed maximum. One way I think helps explain this is the following. Every object is an instance of another meta-level. This implies that there are, in theory, an infinite number of meta-levels. This implies that there would need to be an infinite number of classes to represent this infinite number of meta-levels - not very practical. Instead, the infinite number of meta-levels can be represented by creating a cycle at some point; when one hits this cycle one can choose to go round the cycle as many times as one wishes. So the instance of hierarchy in Converge looks like this:

1 -> Int -> Class -> Class -> Class -> ...

This style of metacircularity is directly derived from Pierre Cointe's work on ObjVLisp in the 80's. There are three or four papers that detail ObjVLisp, but unfortunately they're terse and hard to understand. What's interesting about ObjVLisp is that everything is an object, and that's not just a restating of the normal OO platitude. In an ObjVLisp system, everything really is an object - more accurately, every object in the system is an instance of the Object class. In Converge, as in ObjVLisp, the system is bootstrapped with two objects: the class Object and the class Class. The clever trick inherited from ObjVLisp is that the class Class is a subclass of the class Object. Every object in Converge is by definition an instance of the Object class; but since the class Class is a subclass of Object every class is by subsumption an instance of Object. This can make ones head spin, so here's the two classes in question:

class Object:
  slots : Dict{String : Object}
  
  func init(*args):
    no-op.

class Class(Object):
  name : String
  parent : Class
  methods : List(Method)
  attributes : List(Attribute)
  
  func init(name, parent, attributes, methods) :
    Set class's name, parent, attributes and methods.
	
  func new(*args):
    Create new object, set its instance_of slot and call the new
    objects init slot with args.

Of course, if the class Class is an instance of itself, and inherits from the class Object then how does one ever get a system with these things up and running? And what can you use this stuff for? The latter point is something that I'll leave to a later article. The former I think is best left to an implementation to describe. So here's a simple, fully commented, Python program which implements a complete metacircular system. Take it for what it's worth. I wrote an earlier version of this to explain metacircularity for a project student of mine. A few print statements strategically placed can really bring this stuff to life.

Updated (September 17 2005): Corrected two minor typos.

Follow me on Twitter @laurencetratt

Link to this entry

 

All posts

 

Last 10 posts

An editor for composed programs
The Bootstrapped Compiler and the Damage Done
Relative and Absolute Levels
General Purpose Programming Languages' Speed of Light
Another Non-Argument in Type Systems
Server Failover For the Cheap and Forgetful
Fast Enough VMs in Fast Enough Time
Problems with Software 3: Creating Crises Where There Aren't Any
Problems with Software 2: Failing to Use the Computing Lever
Problems with Software 1: Confusing Problems Whose Solutions Are Easy to State With Problems Whose Solutions Are Easy to Realise
 
 

DSLs

Tony Clark
Zef Hemel
 

Modelling

Mark Delgano
Steven Kelly
Jim Steel
 

OS

Marc Balmer
Ross Burton
Peter Hansteen
OpenBSD Journal
Ted Unangst
 

Programming

Peter Bell
Gilad Bracha
Tony Clark
Cliff Click
William Cook
Jonathan Edwards
Fabien Fleutot
Martin Fowler
John Goerzen
Grace
James Hague
James Iry
JOT
Ralf Laemmel
Lambda the Ultimate
Daniel Lemire
Michael Lucas
Bertrand Meyer
Keith Packard
Havoc Pennington
Brown PLT
John Regehr
Diomidis Spinellis
Shin Tai
Markus Voelter
Phil Wadler
Russel Winder
Steve Yegge