Skip to content

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>
Create a class called name with the specified constructor. The constructor should return a plain table which will be turned into an instance of Class from a call to 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:

  1. Add standard functionality to the class e.g. dash.Cloneable, dash.ShallowEq
  2. Mixin an implementation of an interface e.g. dash.mixin( fns )
  3. 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 string

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


Class.__le

function Class.__le(other) --> string
Returns 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
Returns 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
Run after the instance has been properly initialized, allowing methods on the instance to be used.

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
Returns 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>
Create a subclass of Class with a new name that inherits the metatable of Class, optionally overriding the constructor and providing additional decorators.

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 string

constructor - 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>
Create a subclass of Class with a new name that inherits the metatable of Class, optionally overriding the constructor and providing additional decorators.

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 type

decorators - 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.isInstance

function Class.isInstance(value) --> bool
Returns 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
Return a new instance of the class, passing any arguments to the specified constructor.

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
Return a string representation of the instance. By default this is the name field (or the Class name if this is not defined), but the method can be overridden.

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>
Create a class called name that implements a specific strict interface which is asserted when any instance is created.

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 string

interface - 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 about t, 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


Cloneable

function dash.Cloneable(Class) --> Cloneable<T>
A decorator which derives a :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
Returns a function that creates a shallow copy of the template when called.

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>
Returns a decorator which runs fn on each instance of the class that is created, returning the result of the function as the class instance.

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>
Create an enumeration from an array string keys, provided in upper snake-case.

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
dash.finalize takes object and makes updating or accessing missing keys throw 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>
A decorator which derives a :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
dash.freeze takes object and returns a new read-only version which prevents any values from being changed.

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
Returns 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 value

Type - 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
Given an enum and strategies, a dictionary of functions keyed by enum values, dash.match returns a function that will execute the strategy for any value provided.

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>
A decorator which adds a dictionary of functions to a Class table.

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>
A decorator which derives an order for the Class, meaning instances of the class can be compared using <, <=, > 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>
A decorator which derives the equality operator for the Class so that any instances of the class which are shallow equal will be considered equal.

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


symbol

function dash.symbol(name) --> Symbol<T>
Create a symbol with a specified name. We recommend upper snake-case as the symbol is a constant, unless you are linking the symbol conceptually to a different string.

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}