Slag:Singletons

From Plasmaworks

Jump to: navigation, search

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))