Slag:Singletons
From Plasmaworks
Singletons are a common design pattern in object-oriented languages, and in Slag they're built-in to the language.
A singleton is where you have global access to a single instance of a given class. The effect is similar to a class filled with class variables (aka static variables), but singletons are more powerful because you have an object that you can assign to references, implement interfaces through, and so on.
Slag has two varieties of singleton: the standard singleton and the deferred singleton.
Contents |
Standard Singletons
Standard singletons create the singleton object when the program first starts.
Example Java Singleton
public class ImageCache
{
static ImageCache instance = new ImageCache();
HashMap<String,Image> images = new HashMap<String,Image>();
public Image get( String filename ) { ... }
}
...
Image img = ImageCache.instance.get("laser.png")
Example Slag Singleton
singleton class ImageCache
PROPERTIES
images() : HashTable<<String,Image>>
METHODS
method get( String filename ).Image: ...
endClass
...
local var img = ImageCache["laser.png"]
Deferred Singletons
With a deferred singleton, the singleton object is created when the singleton is first accessed. This is necessary when a singleton's initialization will access resources that may not be available or initialized at the very beginning of a program.
Example Java Deferred Singleton
public class ImageCache
{
static ImageCache instance;
static ImageCache getInstance()
{
if (instance == null) instance = new ImageCache()
return instance;
}
HashMap<String,Image> images = new HashMap<String,Image>();
public Image get( String filename ) { ... }
}
...
Image img = ImageCache.getInstance().get("laser.png")
Example Slag Deferred Singleton
deferred singleton class ImageCache
PROPERTIES
images() : HashTable<<String,Image>>
METHODS
method get( String filename ).Image: ...
endClass
...
local var img = ImageCache["laser.png"]
Tips
- Singletons don't have to have an init() method defined, but if they do then that method is called when the singleton is created.
- The name of a singleton is a reference to it, e.g. "local ImageCache cache = ImageCache".
- Singletons can be set to reference a new object by assigning to their name: "ImageCache = ImageCache(new_params)".
- Extended singletons aren't automatically singletons, and likewise extended classes can be singletons whether or not the originals are. For example:
singleton class Names : String[];
...
Names.add("Abe")
Names.add("Ty")
Names.clear
Caveats
- An initializer method with a default parameter (for example, "init(Int32 capacity=10)") will not be called when a singleton is created. Only an init() method with no parameters will be called.
- Initialization of a singleton containing code that recursively references the singleton will likely produce an error. Singleton references are not properly set until after their initialization code returns. For example:
deferred singleton class World
monsters() : Monster[]
hero() : Hero
endClass
class Hero
PROPERTIES
monsters : Monster[]
METHODS
method init:
monsters = World.monsters
endClass
# This code produces an infinite recursion. The first reference to World
# will create a new World object, which creates a new Hero, which tries
# to access World - but World isn't finished setting up yet and the
# internal singleton reference is null! World tries to set up again
# (recursively), which creates a new Hero, etc.
#
# One way to fix this would be to move the hero instantiation into a
# separate method so it's not performed as World is initialized:
#
# hero : Hero
# METHODS
# method set_up:
# hero = Hero()
# ...
# World.set_up # instantiates World and then calls set_up
Singletons and Class Members
While most languages implement singletons through class members (aka static variables and methods), Slag implements class members through singletons. Slag doesn't truly have such a thing as a class member - class members definitions are placed into separate singletons that become implicit context for resolving member access.
Original Slag Program
class SomeDataStructure
CLASS_PROPERTIES
default_capacity=10 : Int32
PROPERTIES
capacity : Int32
METHODS
method init:
capacity = default_capacity
...
endClass
Post-processed Output Equivalent
singleton class SomeDataStructureManager
PROPERTIES
default_capacity=10 : Int32
endClass
class SomeDataStructure
SINGLETONS
SomeDataStructureManager
PROPERTIES
capacity : Int32
METHODS
method init:
capacity = SomeDataStructureManager.default_capacity
endClass
Compound Managers
Compounds cannot contain methods, but a singleton may be used in conjunction with a compound to fake it.
If a compound such as the following is declared:
compound Vector2( Real64 x, Real64 y )
and it is used as the context of a call:
local Vector2 v(3,4) println( v.magnitude )
Then the Slag compiler will automatically transform the call to have singleton context "Vector2Manager", inserting the compound as the first parameter:
println( Vector2Manager.magnitude(v) )
The actual method definition would look like this:
class Vector2Manager
method magnitude( Vector2 v ).Real64:
return sqrt(v.x*v.x + v.y*v.y)
endClass
Notes
- The same trick works with primitives so long as a compatible method is declared in class Global. For instance, "i.clamped(1,10)" becomes "Global.clamped(i,1,10)".
- Add "create_from(...)" methods into a compound manager to act as alternate initalizers. For example:
class Vector2Manager
method create_from( Real64 r, Radians angle ).Vector2:
return Vector2(r*cos(angle),r*sin(angle))
endClass
...
local Vector2 v1(3,4)
# Directly creates a Vector2 compound
local Vector2 v2(5,Radians(0.927))
# Same as:
# local Vector2 v2 = Vector2Manager.create_from(5,Radians(0.927))