Typer
I’m currently working on a strict type checking system called “typer” that will be open-sourced. Below is some example code using this system! At the bottom of this post I also attached the module source code.
Purpose
I want this module to make type checking easy and precise. This shouldn’t cause a huge interruption to the workflow and should allow for better debugging and stricter values.
Feedback I’m looking for
I’m looking for any feedback that can address any of the following problems, if applicable:
- Design flaws
- Workflow flaws
- Naming sheme
- Usability
Examples
Type casting
Code:
local typer = require(script.Parent.typer)
local cast = typer.cast
local valueA = cast("hello!") "string"
local valueB = cast(10) "number"
print("valueA", valueA)
print("valueB", valueB)
local valueC = cast("foo") "table"
print("valueC", valueC)
Output:
Casted value EQ
Code:
local typer = require(script.Parent.typer)
local cast = typer.cast
local casteq = typer.casteq
local valueA = cast("hello!") "string"
local valueB = "foo"
local valueC = cast(10) "number"
local valueD = "foo"
if casteq(valueA, valueB) then
print("valueA = valueB")
end
if casteq(valueB, valueD) then
print("valueA = valueD")
end
if casteq(valueB, valueC) then
print("valueB = valueC")
end
Output:
Immutable environments
Code:
local typer = require(script.Parent.typer)
local cast = typer.cast
local env = typer.env
local testEnvironment = env()
testEnvironment.a = 5
testEnvironment.b = 7
testEnvironment.c = 12
testEnvironment("printValue", "a")
testEnvironment("printAll")
testEnvironment("dumpValue", "a")
testEnvironment("printValue", "a")
testEnvironment("dumpAll")
testEnvironment("printAll")
testEnvironment.a = 8
testEnvironment.b = 19
testEnvironment.b = 21
Output:
Soft casting
Code:
local typer = require(script.Parent.typer)
local expect = typer.expect
expect(5, 12, 15).toBe("number", "number", "number").andIfSo(function()
print("all types match in function #1")
end).andIfNot(function(output)
print("not all types match in function #1, output:", output)
end)
expect(5, nil, {1, 2, 3}, "hiya").toBe("number", "nil", "number", "string").andIfSo(function()
print("all types match in function #2")
end).andIfNot(function(output)
print("not all types match in function #2, output:", output)
end)
Output:
Tuple assertion
Code:
local typer = require(script.Parent.typer)
local tupleAssertion = typer.tupleAssertion
local assertType1 = tupleAssertion("number", "number")
local assertType2 = tupleAssertion("string", "table", "number")
local function testFunction1(a, b)
assertType1(a, b)
return a + b -- Can safely do this if the above doesn't error
end
local function testFunction2(a, b, c)
assertType2(a, b, c)
return string.len(a) + #b + c -- Can safely do this if the above doesn't error
end
local value1 = testFunction1(1, 5)
print(value1)
local value2 = testFunction2("hello world!", {"a", 1, true}, 4)
print(value2)
local value3 = testFunction2("oops!", 2, {})
print(value3)
Output:
Static functions
Code:
local typer = require(script.Parent.typer)
local static = typer.static
local testFunction = static(function(a, b, c)
return a + b, if c == true then 1 else "erp"
end).withParameterTypes("number", "number", "boolean").withReturnTypes("number", "number").asserted()
local value1 = testFunction(1, 2, true)
print(value1)
local value2 = testFunction(3, 4, false)
print(value2)
local value3 = testFunction("this would also normally error")
print(value3)
Output:
Source
Source code module
local typer = {}
function typer.static(callbackFunction : (any?) -> any?)
local self = {}
local parameterAssertion = nil
local returnAssertion = nil
function self.withParameterTypes(...)
parameterAssertion = typer.tupleAssertion(...)
return self
end
function self.withReturnTypes(...)
returnAssertion = typer.tupleAssertion(...)
return self
end
function self.asserted()
if parameterAssertion and returnAssertion then
return function(...)
local args = {...}
if parameterAssertion then
parameterAssertion(unpack(args))
end
local returnedValues = {callbackFunction(...)}
if returnAssertion then
local success, output = pcall(function()
returnAssertion(unpack(returnedValues))
end)
if not success then
error(`An error occurred when expecting return types from static function: {output}`)
end
end
return unpack(returnedValues)
end
end
end
return self
end
function typer.cast(value : any) : () -> any
return function(... : string) : any
if not table.find({...}, typeof(value)) then
error(`Expected casted value to be a {table.concat({...}, ", or ")}, instead got a {typeof(value)} type`)
end
return value
end
end
function typer.casteq(a : any, b : any)
if typeof(a) ~= typeof(b) then
error(`Attempted to compare a {typeof(a)} to a {typeof(b)}`)
end
return a == b
end
function typer.tupleAssertion(... : string)
local types = {...}
return function(... : any)
local args = {...}
for index, type_ in types do
if typeof(args[index]) ~= type_ then
error(`Tuple #{index} in function does not match type; expected a {type_}, got a {typeof(args[index])}.`)
end
end
end
end
function typer.expect(... : any)
local self = {}
local args = {...}
local success, output
function self.toBe(... : string)
local types = {...}
success, output = pcall(function()
typer.tupleAssertion(unpack(types))(unpack(args))
end)
return self
end
function self.andIfNot(callback : (any?) -> nil)
if not success then
callback(output)
end
return self
end
function self.andIfSo(callback : (any?) -> nil)
if success then
callback(output)
end
return self
end
return self
end
function typer.env() : typeof(setmetatable({},{}))
local self = {}
return setmetatable({}, {
__index = function(_, index)
if not self[index] then
warn(`Attemped to access an immutable value named "{index}" but it does not exist, returning nil`)
end
return self[index]
end,
__newindex = function(_, index, value)
if self[index] then
error("Attemped to modify an immutable value")
end
self[index] = value
if typeof(value) == "Instance" then
value.AncestryChanged:Connect(function()
if value:FindFirstAncestor(game) == nil then
self[index] = nil
end
end)
end
end,
__call = function(_, command, ...)
({
dumpAll = function()
self = {}
end,
dumpValue = function(index)
self[index] = nil
end,
printAll = function()
print(self)
end,
printValue = function(index)
print(self[index])
end,
})[command](...)
end,
})
end
return typer