Classes¶
These tools provide implementations of and functions for higher-order abstractions such as classes, enumerations and symbols.
Functions¶
class¶
function dash.class(name, constructor, decorators) --> Class<T>
Class.new(...)
.
Optionally, you may provide an array of decorators which compose and reduce the Class, adding additional methods and functionality you may need. Specifically you can:
- Add standard functionality to the class e.g. dash.Cloneable, dash.ShallowEq
- Mixin an implementation of an interface e.g.
dash.mixin( fns )
- Decorate fields or functions e.g.
dash.decorate(dash.freeze)
Type
<T>(string, Constructor<T>?, Decorator<T>[]?) -> Class<T>
Generics
T -
any
- the primary type (extends any value)
Parameters
name -
string
- a stringconstructor -
Constructor<T>?
- a Constructor (of the primary type) (optional) - (default =dash.construct({})
)decorators -
Decorator<T>[]?
- an array (of Decorators (of the primary type)) (optional) - (default ={}
)
Returns
Class<T>
- a Class (of the primary type)
Examples
-- Create a simple Vehicle class local Vehicle = dash.class("Vehicle", function(wheelCount) return { speed = 0, wheelCount = wheelCount } end) function Vehicle:drive(speed) self.speed = speed end -- Create a car instance local car = Vehicle.new(4) car.wheelCount --> 4 car.speed --> 0 -- Drive the car car:drive(10) car.speed --> 10
Usage
-
When using Rodash classes, private fields should be prefixed with
_
to avoid accidental access. -
A private field should only be accessed by a method of the class itself, though Rodash does not restrict this in code.
-
Public fields are recommended when there is no complex access logic e.g.
position.x
See
-
dash.classWithInterface - recommended for providing runtime type-checking.
-
dash.mixin - extend the class with extra methods.
-
dash.decorate - include methods that run when an instance of the class is constructed.
-
dash.construct - provide a default object to use as a new instance.
Class.__le¶
function Class.__le(other) --> string
true
if self
is considered less than or equal to other. This replaces the
<=
operator on instances of this class, and can be overridden to provide a custom
implementation.
Type
T -> string
Parameters
other -
T
- the type of self
Returns
string
- a string
Class.__lt¶
function Class.__lt(other) --> string
true
if self
is considered less than other. This replaces the <
operator
on instances of this class, and can be overridden to provide a custom implementation.
Type
T -> string
Parameters
other -
T
- the type of self
Returns
string
- a string
Class._init¶
function Class._init() --> void
Type
(mut self) -> ()
Returns
void
- nothing
Examples
local Vehicle = dash.class("Vehicle", function(wheelCount) return { speed = 0, wheelCount = wheelCount } end) -- Let's define a static private function to generate a unique id for each vehicle. function Vehicle._getNextId() Vehicle._nextId = Vehicle._nextId + 1 return Vehicle._nextId end Vehicle._nextId = 0 -- A general purpose init function may call other helper methods function Vehicle:_init() self._id = self:_generateId() end -- Assign an id to the new instance function Vehicle:_generateId() return dash.format("#{}: {} wheels", Vehicle._getNextId(), self.wheelCount) end -- Return the id if the instance is represented as a string function Vehicle:toString() return self._id end local car = Vehicle.new(4) tostring(car) --> "#1: 4 wheels"
Class.equals¶
function Class.equals(other) --> bool
true
if self
is considered equal to other. This replaces the ==
operator
on instances of this class, and can be overridden to provide a custom implementation.
Type
T -> bool
Parameters
other -
T
- the type of self
Returns
bool
- a boolean
Class.extend¶
function Class.extend(name, constructor, decorators) --> Class<S>
The super-constructor can be accessed with undefined.
Super methods can be accessed using Class.methodName
and should be called with self.
Type
<S: T>(string, Constructor<S>?, Decorator<S>[]?) -> Class<S>
Generics
S -
T
- the subject type (extends the type of self)
Parameters
name -
string
- a stringconstructor -
Constructor<S>?
- a Constructor (of the subject type) (optional)decorators -
Decorator<S>[]?
- an array (of Decorators (of the subject type)) (optional)
Returns
Class<S>
- a Class (of the subject type)
Examples
local Vehicle = dash.class("Vehicle", function(wheelCount) return { speed = 0, wheelCount = wheelCount } end) -- Let's define a static private function to generate a unique id for each vehicle. function Vehicle._getNextId() Vehicle._nextId = Vehicle._nextId + 1 return Vehicle._nextId end Vehicle._nextId = 0 -- A general purpose init function may call other helper methods function Vehicle:_init() self.id = self:_generateId() end -- Assign an id to the new instance function Vehicle:_generateId() return dash.format("#{}: {} wheels", Vehicle._getNextId(), self.wheelCount) end -- Let's make a Car class which has a special way to generate ids local Car = Vehicle:extend("Vehicle", function() return Vehicle.constructor(4) end) -- Uses the super method to generate a car-specific id function Car:_generateId() self.id = dash.format("Car {}", Vehicle._generateId(self)) end local car = Car.new() car.id --> "Car #1: 4 wheels"
Class.extendWithInterface¶
function Class.extendWithInterface(name, interface, decorators) --> Class<S>
Type
<S: T>(T: string, S, Decorator[]?) -> Class<S>
Generics
S -
T
- the subject type (extends the type of self)
Parameters
name -
T: string
- T (a string)interface -
S
- the subject typedecorators -
Decorator[]?
- an array (of Decorators) (optional)
Returns
Class<S>
- a Class (of the subject type)
Examples
local Vehicle = dash.classWithInterface("Vehicle", { color = t.string }) local Car = Vehicle:extendWithInterface("Car", { bootContents = t.array(t.string) }) local car = Car.new({ color = "red", bootContents = "tyre", "bannana" }) dash.pretty(car) --> 'Car {bootContents = {"tyre", "bannana"}, color = "red"}'
Usage
- Interfaces currently silently override super interfaces, even if their types are incompatible. Avoid doing this as more advanced type checking may throw if the types do not unify in the future.
See
-
Class.extend
-
The t library - used to check types at runtime.
Class.isInstance¶
function Class.isInstance(value) --> bool
true
if value is an instance of Class or any sub-class.
Type
any -> bool
Parameters
value -
any
- any value
Returns
bool
- a boolean
Examples
local Vehicle = dash.class("Vehicle", function(wheelCount) return { speed = 0, wheelCount = wheelCount } end) local Car = Vehicle:extend("Vehicle", function() return Vehicle.constructor(4) end) local car = Car.new() car.isInstance(Car) --> true car.isInstance(Vehicle) --> true car.isInstance(Bike) --> false
Class.new¶
function Class.new(...) --> S
Type
<S, A>(...A -> S)
Generics
S -
any
- the subject type (extends any value)A -
any
- the primary arguments (extends any value)
Parameters
... -
...A
- the primary arguments
Returns
S
- the subject type
Examples
local Car = dash.class("Car", function(speed) return { speed = speed } end) local car = Car.new(5) dash.pretty(car) --> 'Car {speed = 5}'
Class.toString¶
function Class.toString() --> string
Type
() -> string
Returns
string
- a string
Examples
local Car = dash.class("Car", function(name) return { name = name } end) local car = Car.new() car:toString() --> 'Car' tostring(car) --> 'Car' print("Hello " .. car) -->> Hello Car local bob = Car.new("Bob") bob:toString() --> 'Bob' tostring(bob) --> 'Bob' print("Hello " .. bob) -->> Hello Bob
local NamedCar = dash.class("NamedCar", function(name) return { name = name } end) function NamedCar:toString() return "Car called " .. self.name end local bob = NamedCar.new("Bob") bob:toString() --> 'Car called Bob' tostring(bob) --> 'Car called Bob' print("Hello " .. bob) -->> Hello Car called Bob
classWithInterface¶
function dash.classWithInterface(name, interface, decorators) --> Class<T>
Instead of using a constructor, an instance is initialized with a table containing the required
fields. If an _init
method is present on the instance, this is called afterwards, which has
the added benefit over a constructor that self
and the instance are well-defined.
Rodash uses The t library to check types at runtime, meaning
the interface must be a dictionary of keys mapped to type assertion functions, such as
t.number
, dash.isCallable etc.
Optionally, you may provide an array of decorators which are reduced with the Class, adding additional functionality in the same way dash.class does. See Decorator for more information.
Type
<T>(string, Interface<T>, Decorator<T>[]? -> Class<T>)
Generics
T -
any
- the primary type (extends any value)
Parameters
name -
string
- a stringinterface -
Interface<T>
- an Interface (of the primary type)decorators -
Decorator<T>[]?
- an array (of Decorators (of the primary type)) (optional)
Returns
Class<T>
- a Class (of the primary type)
Examples
local Vehicle = dash.classWithInterface("Vehicle", { speed = t.number, wheelCount = t.number, color = t.string }) local vehicle = Vehicle.new({ speed = 5, wheelCount = 4, color = "red" }) dash.pretty(vehicle) --> 'Vehicle {speed = 4, wheelCount = 4, color = "red"}'
Usage
-
Rodash uses
t
by Osyris to perform runtime type assertions, which we recommend using in your own code development and production to catch errors quickly and fail fast. For more information aboutt
, please visit https://github.com/osyrisrblx/t. -
If you want to instantiate private fields, we recommend using a static factory with a public interface, See dash.privatize for an example.
See
-
The t library - used to check types at runtime.
Cloneable¶
function dash.Cloneable(Class) --> Cloneable<T>
:clone()
method for the Class that returns a shallow clone of
the instance when called that has the same metatable as the instance it is called on.
Type
<T>(Class<T> -> Cloneable<T>)
Generics
T -
any
- the primary type (extends any value)
Parameters
Class -
Class<T>
- a Class (of the primary type)
Returns
Cloneable<T>
- a Cloneable (of the primary type)
Examples
local Car = Classes.class( "Car", function(speed) return { speed = speed } end, {dash.Cloneable} ) function Car:brake() self.speed = 0 end local car = Car.new(5) local carClone = car:clone() print(carClone.speed) --> 5 carClone:brake() print(carClone.speed) --> 0 print(car.speed) --> 5
construct¶
function dash.construct(template) --> (void) -> T
Type
<T:{}>(T -> () -> T)
Generics
T -
{}
- the primary type (extends a table)
Parameters
template -
T
- the primary type
Returns
(void) -> T
- a function (taking nothing, and returning the primary type)
Examples
local Car = dash.class("Car", dash.construct({ speed = 5 })) local car = Car.new() car.speed --> 5
-- A constructor can be made for a class with an interface using decorate. local constructor = dash.construct({ speed = 5 }) local Car = dash.classWithInterface("Car", { speed = t.optional(t.number) }, {dash.decorate(constructor)}) local car = Car.new() car.speed --> 5
Usage
- Useful if you want to return a default object for a class constructor.
See
decorate¶
function dash.decorate(fn) --> (Class<T>) -> Class<T>
Type
<T>((self:T, ... -> ...) -> Class<T> -> Class<T>)
Generics
T -
any
- the primary type (extends any value)
Parameters
fn -
(self: T, ...any) -> ...any
- a function (taking self (the primary type) and any values, and returning any values)
Returns
(Class<T>) -> Class<T>
- a function (taking a Class (of the primary type), and returning a Class (of the primary type))
Examples
-- Create a decorator which freezes all class keys local Frozen = dash.decorate(dash.freeze) local StaticCar = dash.class("StaticCar", function(speed) return { speed = speed } end, {Frozen}) function StaticCar:brake() self.speed = 0 end local car = Car.new(5) print(car.speed) --> 5 -- The car cannot change speed because speed is now readonly. car:brake() --!> ReadonlyKey: Attempt to write to a frozen key speed
Usage
- Include the return value of this function in the decorators argument when creating a class.
See
enum¶
function dash.enum(keys) --> Enum<T>
An Enum is used when a value should only be one of a limited number of possible states. dash.enum creates a string enum, which uses a name for each state so it is easy to refer to. For ease of use values in the enum are identical to their key.
Enums are frozen and will throw if access to a missing key is attempted, helping to eliminate typos.
Symbols are not used so that enum values are serializable.
Type
<T>(string -> Enum<T>)
Generics
T -
any
- the primary type (extends any value)
Parameters
keys -
string
- a string - provided in upper snake-case.
Returns
Enum<T>
- an Enum (of the primary type)
Examples
local TOGGLE = dash.enum("ON", "OFF") local switch = TOGGLE.ON if switch == TOGGLE.ON then game.Workspace.RoomLight.Brightness = 1 else game.Workspace.RoomLight.Brightness = 0 end
See
finalize¶
function dash.finalize(object) --> T
FinalObject
.
Type
<T:{}>(mut T -> T)
Generics
T -
{}
- the primary type (extends a table)
Parameters
object -
mut T
- the primary type (which can be mutated)
Returns
T
- the primary type
Examples
local drink = { mixer = "coke", spirit = "rum" } dash.finalize(drink) drink.mixer = "soda" drink.mixer --> "soda" print(drink.syrup) --!> "FinalObject: Attempt to read missing key syrup to final object" drink.syrup = "peach" --!> "FinalObject: Attempt to add key mixer on final object"
Formatable¶
function dash.Formatable(keys) --> (Class<T>) -> Formatable<T>
:toString()
method for the Class that displays a serialized
string of the class name and values of any keys provided.
Type
<T>(string[]? -> Class<T> -> Formatable<T>)
Generics
T -
any
- the primary type (extends any value)
Parameters
keys -
string[]?
- an array (of strings) (optional) - (default = all the keys of the instance except"Class"
)
Returns
(Class<T>) -> Formatable<T>
- a function (taking a Class (of the primary type), and returning a Formatable (of the primary type))
Examples
local Car = Classes.class( "Car", function(speed) return { speed = speed, color = "red" } end, {dash.Formatable({"speed"})} ) print(Car.new(5)) --> "Car({speed:5})"
freeze¶
function dash.freeze(object) --> T
Unfortunately you cannot iterate using pairs
or ipairs
on frozen objects because Lua 5.1
does not support overwriting these in metatables. However, you can use dash.iterator to get
an iterator for the object and use that.
Iterating functions in Rodash such as dash.map, dash.filter etc. can iterate over frozen objects
without this. If you want to treat the objects as arrays use dash.iterator(frozenObject, true)
explicitly.
Type
<T: table>(T -> T)
Generics
T -
table
- the primary type (extends a table)
Parameters
object -
T
- the primary type
Returns
T
- the primary type
Examples
local drink = dash.freeze({ flavor = "mint", topping = "sprinkles" }) print(drink.flavor) --> "mint" drink.flavor = "vanilla" --!> "ReadonlyKey: Attempt to write to a frozen key flavor" print(drink.syrup) --> nil drink.topping = "flake" --!> "ReadonlyKey: Attempt to write to a frozen key topping" dash.values(drink) --> {"mint", "sprinkles"}
See
isA¶
function dash.isA(value, Type) --> bool
true
if value is an instance of type.
Type can currently be either an Enum or a Class table. For instances of classes, dash.isA will also return true if the instance is an instance of any sub-class.
The function will catch any errors thrown during this check, returning false if so.
Type
<T>(any, Type<T> -> bool)
Generics
T -
any
- the primary type (extends any value)
Parameters
value -
any
- any valueType -
Type<T>
- a Type (of the primary type)
Returns
bool
- a boolean
Examples
local Vehicle = dash.class("Vehicle", function(wheelCount) return { speed = 0, wheelCount = wheelCount } end) local car = Vehicle.new(4) Vehicle.isA(car) --> true Vehicle.isA(5) --> false
local TOGGLE = dash.enum("ON", "OFF") TOGGLE.isA("ON") --> true TOGGLE.isA(5) --> false
Usage
- This is useful if you know nothing about value.
match¶
function dash.match(enum, strategies) --> (enumValue: T, ...A) -> V
A strategy for every enum key must be implemented, and this helps prevent missing values from causing problems later on down the line.
Type
<T, ...A, V>(Enum<T>, {[enumValue: T]: Strategy<V, A>}) -> (enumValue: T, ...A) -> V
Generics
T -
any
- the primary type (extends any value)A -
any
- the primary arguments (extends any value)V -
any
- the primary value type (extends any value)
Parameters
enum -
Enum<T>
- an Enum (of the primary type)strategies -
{[enumValue: T]: Strategy<V, A>}
- a dictionary mapping enumValue (the primary type) to Strategys (of the primary value type and the primary arguments)
Returns
(enumValue: T, ...A) -> V
- a function (taking enumValue (the primary type) and the primary arguments, and returning the primary value type)
Examples
local TOGGLE = dash.enum("ON", "OFF") local setLightTo = dash.match(TOGGLE, { ON = function(light) light.Brightness = 1 end, OFF = function(light) light.Brightness = 0 end }) -- This can be used to turn any light on or off: setLightTo(TOGGLE.ON, game.Workspace.RoomLight) -- Light turns on -- But will catch an invalid enum value: setLightTo("Dim", game.Workspace.RoomLight) --!> BadInput: enumValue must be an instance of enum
mixin¶
function dash.mixin(fns) --> (Class<T>) -> Class<T>
Type
<T>((T, ... -> ...)[] -> Class<T> -> Class<T>)
Generics
T -
any
- the primary type (extends any value)
Parameters
fns -
(T, ...any) -> ...any[]
- an array (of functions (taking the primary type and any values, and returning any values))
Returns
(Class<T>) -> Class<T>
- a function (taking a Class (of the primary type), and returning a Class (of the primary type))
Examples
local CanBrake = { brake = function(self) self.speed = 0 end } local Car = dash.class("Car", function(speed) return { speed = speed } end, {dash.mixin(CanBrake)}) local car = Car.new(5) print(car.speed) --> 5 car:brake() print(car.speed) --> 0
Usage
- Include the return value of this function in the decorators argument when creating a class.
Ord¶
function dash.Ord(keys) --> (Class<T>) -> Ord<T>
<
, <=
, >
and >=
. To do this, it compares values of the two
instances at the same keys, as defined by the order of the keys passed in.
Type
<T>(string[]? -> Class<T> -> Ord<T>)
Generics
T -
any
- the primary type (extends any value)
Parameters
keys -
string[]?
- an array (of strings) (optional) - (default = a sorted array of all the instance's keys)
Returns
(Class<T>) -> Ord<T>
- a function (taking a Class (of the primary type), and returning an Ord (of the primary type))
Examples
local Car = Classes.class( "Car", function(speed) return { speed = speed } end, {dash.Ord()} ) function Car:brake() self.speed = 0 end local fastCar = Car.new(500) local fastCar2 = Car.new(500) local slowCar = Car.new(5) print(fastCar == fastCar2) --> true print(fastCar == slowCar) --> false
ShallowEq¶
function dash.ShallowEq(Class) --> Eq<T>
Type
<T>(Class<T> -> Eq<T>)
Generics
T -
any
- the primary type (extends any value)
Parameters
Class -
Class<T>
- a Class (of the primary type)
Returns
Eq<T>
- an Eq (of the primary type)
Examples
local Car = Classes.class( "Car", function(speed) return { speed = speed } end, {dash.ShallowEq} ) function Car:brake() self.speed = 0 end local fastCar = Car.new(500) local fastCar2 = Car.new(500) local slowCar = Car.new(5) print(fastCar == fastCar2) --> true print(fastCar == slowCar) --> false
Usage
- If you want to check for equality that includes deep descendants, we recommend you
implement a custom
:equals
method on your class rather than use dash.deepEqual as this may be slow or fail to check the type of instances in the tree.
See
-
dash.deepEqual - if you want to consider deep equality
-
Class:equals
symbol¶
function dash.symbol(name) --> Symbol<T>
Symbols are useful when you want a value that isn't equal to any other type, for example if you want to store a unique property on an object that won't be accidentally accessed with a simple string lookup.
Type
<T>(string -> Symbol<T>)
Generics
T -
any
- the primary type (extends any value)
Parameters
name -
string
- a string
Returns
Symbol<T>
- a Symbol (of the primary type)
Examples
local DOUBLE = dash.symbol("DOUBLE") -- This function acts like filter, but doubles any elements in a row for which DOUBLE is -- returned rather than true or false. local function filterWithDouble(array, fn) return dash.flatMap(array, function(element) local result = fn(element) if result == DOUBLE then return {result, result} elseif result then return {result} else return {} end end) end local array = {1, 2, 3, 4, 5} local function isEven(value) return value % 2 == 0 end filterWithDouble(array, isEven) --> {1, 2, 2, 3, 4, 4, 5}