Wednesday, March 12, 2008

Singletons in Factor

A singleton is a design pattern that only allows for a unique class that is only instantiated once. In other languages, singletons can contain global data, but why not just use a global to store global state instead?

In Factor, a singleton is simply a predicate class whose predicate tests if the class is identical to itself. Singletons are defined like this:
SINGLETON: factor
That's it! No messy boilerplate to copy and paste, no subtle reasons why your singleton might be wrong in some corner case. Using the singleton, we can replace some of the less elegant parts of the Factor core code and implement things in a simpler way.

For instance, until now the core words os and cpu have returned strings based on how the Factor binary is compiled. Soon, these strings will be parsed into whichever singleton they represent, allowing for generic dispatch. I wanted to have a cross-platform library for finding out basic hardware information about your computer, like how many cpus/cores, what speed, and how much RAM is in a machine. To do this without singletons, I had to redefine information already available as strings (the cpu and os words) as symbols. With singletons, this duplication can be removed. The same applies to the compiler code and loading libraries.

Here is the way the core supported operating systems can be defined using singletons.
SINGLETON: winnt
SINGLETON: wince
SINGLETON: macosx
SINGLETON: linux
UNION: windows winnt wince ;
Now we can dispatch on these to find the number of cores:
HOOK: #cpus os ( -- n )
M: macosx #cpus { 6 3 } sysctl-query-uint ;
M: winnt #cpus system-info SYSTEM_INFO-dwNumberOfProcessors ;
For loading code, the current idiom is this:
<< "alut" {
{ [ win32? ] [ "alut.dll" ] }
{ [ macosx? ] [ "/System/Library/Frameworks/OpenAL.framework/OpenAL" ] }
{ [ unix? ] [ "libalut.so" ] }
} cond "cdecl" add-library >>
Using singletons, we can shorten this to:
"alut" {
{ win32 "alut.dll" }
{ macosx "/System/Library/Frameworks/OpenAL.framework/OpenAL" }
{ unix "libalut.so" }
} add-library
The library is assumed to be 'cdecl', but if it were 'stdcall' you could specify this by adding a "stdcall" string after the libary name, thus making a triple instead of a pair. The amount of boilerplate is reduced and the programmer can be more productive and write fewer bugs.

The implementation of singleton is:
: define-singleton-class ( class -- )
\ word swap
dup [ eq? ] curry define-predicate-class ;
This generates code that looks like:
PREDICATE: word winnt \ winnt eq? ;
It makes a predicate class with a superclass of 'word' that you can dispatch on and only a single instance exists. Why are singletons so hard to define in some other languages?

2 comments:

Adam said...

Are you going to put SINGLETON: foo in wikipedia and watch it get removed? ;-)

Chris Double said...

In Dylan a singleton is a type for a specific instance of an object. They are the same as EQL types in CLOS. So you can dispatch on the 'type' for the integer 42 for example:

define method foo( x :: singleton(5))
...
end

So you could use symbols:

define method foo ( os :: singleton("win32"))
...
end

define method foo ( os :: singleton("linux"))
...
end

Called like:
foo("win32");
foo("linux");

(Note: pseudo dylan syntax as I've forgotten it :)