The MiniScript language does not have constructors; when you create a new object with new, like
okBtn = new PushButton
...what you get back is exactly a map with __isa set, like:
{"__isa": PushButton}
So, hooray for simplicity! But boo for wanting to do any more sophisticated initialization when your object is created. In other languages, you'd likely do such initialization in a constructor (a method that automatically runs when you create an instance of a class). But MiniScript doesn't have those. So what do you do?
Option 1: Separate init method
The first and most obvious remedy is to have a separate init method that you call after creating each object. You can find an example of this in the Asteroids demo at /sys/demo/asteroids. Line 37 defines GameSprite as a subclass of Sprite (GameSprite = new Sprite), and then lines 55-59 give it an init method:
GameSprite.init = function
self.v = {"x":0, "y":0}
self.destroyed = false
display(4).sprites.push self
end function
Then later, it makes a subclass of GameSprite called TimedSprite, to represent a game sprite that despawns after a little while:
TimedSprite = new GameSprite
TimedSprite.despawnTime = 0
TimedSprite.init = function(duration = 0.5)
super.init
self.despawnTime = time + duration
end function
(TimedSprite also has a custom update method, but that's not important here.) Note how TimedSprite.init calls super.init, ensuring that the base class gets a chance to do its usual initialization. Similarly, when we make a subclass of TimedSprite called Bullet, it does the same thing:
Bullet = new TimedSprite
Bullet.instances = []
Bullet.init = function(lifetime = 0.6)
super.init lifetime
Bullet.instances.push self
end function
Then when the player presses the fire button, we create a new bullet with the create, then initialize pattern (line 116-117):
b = new Bullet
b.init
You can find other examples of this pattern in some other demos, like fatbits and flappyBat. This pattern works. But it's no longer my favorite solution.
Option 2: Static factory method
The other basic option is to never have callers use new directly, but instead to call a factory method on the class. A factory method is nothing more or less than a method whose job it is to create and return an object. And a "static" method is one that you call on the class, rather than on some specific object. I've lately settled into the habit of naming static methods and properties with a capital first letter, to distinguish them from ordinary instance methods and properties, and most typically, I call my factory method Make.
So this solution is just: stick a Make method on your class that creates, initializes, and returns a new instance.
You can find examples of this pattern in /sys/demo/soundLab, which first makes a RectControl class, and then makes several subclasses of that, including PushButton:
PushButton = new RectControl
PushButton.Instances = []
PushButton.image = desktopPic.getImage(248,106, 20,20)
PushButton.icon = null
PushButton.Make = function(left, bottom, image=null, icon=null)
noob = new self
noob.left = left
noob.bottom = bottom
noob.icon = icon
if image then noob.image = image
noob.width = noob.image.width
noob.height = noob.image.height
PushButton.Instances.push noob
return noob
end function
Be sure to notice the neat trick here: to actually create the new instance, this Make method says noob = new self. Here self is whatever class the Make method was called on; so if the user said PushButton.Make, then new self is equivalent to new PushButton. But if they call Make on some subclass of PushButton, then this new will instantiate that subclass instead.
Of course we're only using new here at all because the base class (RectControl) did not define a Make method. What if it does? Well, let's look at the WaveButton class, which derives from PushButton:
WaveButton = new PushButton
WaveButton.Make = function(left, bottom, imageIdx=1, iconIdx=0)
return super.Make(left, bottom, WaveButton.Images[imageIdx], WaveButton.Icons[iconIdx])
end function
The WaveButton.Make method just calls super.Make to create the new instance. In this case, we only overrode Make in order to pass the appropriate button image and icon. But we could have also done something like:
WaveButton.Make = function(left, bottom, imageIdx=1, iconIdx=0)
noob = super.Make(left, bottom, WaveButton.Images[imageIdx], WaveButton.Icons[iconIdx])
// ...do other initialization with noob here...
return noob
end function
Why Factory Methods Rock
A strong case can be made that factory methods are actually superior to constructors. Here are some reasons why:
- You can have multiple factory methods on the same class, with unique names making it clear what each is for:
MakeForStorageorMakeFromFileor whatever. - A factory method doesn't have to actually create a new object; it could pull an old object out of a recycling pool, or some sharable instance, and the caller doesn't need to know or care.
- A factory method could return a different type based on the parameter values: maybe you need a different class for high-speed sprites (like bullets) vs stationary sprites (like trees). That logic can be hidden inside the factory method, so callers don't have to remember it.
- A factory method can fail gracefully, returning
nullor (in MiniScript 2) anderrorin place of a new object.newcan never do that. - Factories are easier to evolve. You can change the return type, caching policy, initialization steps, etc., without having to update all the callers.
Some of these benefits would also apply to the init method, but most of them would not.
Conclusion
New MiniScript programmers are sometimes surprised by the lack of a constructor feature. But now that you know how to live with it, it's not a lack at all! Just a different way of doing things.
What's your favorite approach to creating & initializing objects in MiniScript? Share your thoughts below!

Top comments (1)
Picking on your
PushButtonexample:MiniScript doesn't care if I reassign
self, and reusing it like this makes my life easier. Remembering to place thatreturn selfat the end is usually where the errors occur for me.