Singletons in Factor
Wednesday, March 12, 2008
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?