Second, what’s the benefit to type-checking? Isn’t it much simpler to have versatile variables? There’s some cases where you want type function to change to type nil etc. Even disregarding those situations, this just takes up time. What’s the benefit?
source
TL;DR: performance-wise, no benefits. yet!
On the convenient side (which I think is the best part), you are type safe. No more type errors because you gave a function on line 20 an argument of wrong type on line 500, or gave a library function wrong arguments, or perhaps you want intellisense for an object! this is why 60% of JS developers switched to TS, and 22% want to, when it doesn’t even have performance benefits as it just compiles straight to JS
Or when you don’t remember the methods, events or properties of an object, instead of switching to a browser, searching for the class, opening up the devhub, scrolling to find the method/property/event, then clicking on it to see what it does/how to use it, type checking allows you to have intellisense so you can quickly see everything the object has type intellisense isn’t out yet on Studio, but you can use VSC (a way better editor) with the Roblox LSP extension to get it
You probably aren’t typing idiomatic code. But if you actually wanna do that, if I was you (not trying to compare), I’d gladly sacrifice 5 seconds of my time to add the type union!
The time you save with the convenience benefits definitely outweighs adding type notations! (^▽^)
This is a really helpful tutorial and I’m sad I didn’t know of its existence sooner. Lua(u) is the only language I’ve ever programmed in so I’ve missed out on all the goodies (and annoyances?) of other languages. Typechecking has been out for a while but I’ve only ever known how to define the types of arguments and returns of functions, nothing else.
This dumbs down the content really easily since I couldn’t understand the typechecking documentation too well due to my unfamiliarity with types and all. I really wanted to incorporate typechecking for cleaner code, the potential future improvements and to leverage script analysis (especially in VS Code) but since I couldn’t grasp it I ended up shelving it and didn’t feel motivated to revisit it. Now I can start investing more time into applying typechecking in current and future code I write.
Excuse me if I’m wrong since I’m only just learning about type checking, but couldn’t the issue in your example be fixed by adding the ? operator to the type number?
type fun = (string, number?) -> string
if I’m not mistaken, that should allow you to use the types number and nil in place of the function’s second argument.
Yes, that’d fix it. But with that post I made is to show that noting the types in the custom function type didn’t propagate to functions noted with it.
With the new type-on-hover feature, it appears it makes it generic:
(and it infers the return type from the body of the function, instead of the type too)
Is there a way to type check for metatables? Currently, I don’t have a solution on how it works.
These are some ways I tried, to no avail
type met<t, mt> = typeof(
setmetatable(t, mt) -- Unkown global 't', Unkown global 'mt'
)
type met = typeof(
setmetatable(
{},
{__index: typeof(function() end)} -- Unkown global '__index'
)
)
type t = {}
type mt = {
__index: typeof(function() end)
}
local foo: typeof(setmetatable(t, mt)) = setmetatable( -- Unkown global 't', Unkown global 'mt'
{},
{
__index = function()
end,
}
)
type t = {}
type mt = {
__index: typeof(function() end)
}
local foo: t & mt = setmetatable( -- Type 'foo' could not be converted into type 'mt'
{},
{
__index = function()
end,
}
)
type OOPType = {
Print: (OOPType, string) -> nil
-- The type must be declared as first parameter to consider the use of "self"
}
If you don’t write the type inside the function declaration as the first parameter, the linter will throw a warning saying "This function does not take self, did you mean to use ."
Metatables inherently isn’t a value type, they’re just normal tables assigned to another table to give functionality so I don’t think they would add such
although a solution if you really need to
type func = type(function() end)
type metatable = {
__index : func | {any},
__newindex : func : {any},
__call : func,
__tostring : func,
__metatable : string,
__mode : string
}
local function getmetatable(...) : metatable?
return getmetatable(...)
end
can you explain the logical thought process of the computer when you tell it to do something using “::” (assertion) because while i can understand the other type operator on a basic level, the way you explained it using “this works” “this does not work” formula for assertion had me on the ropes
Great tutorial!
I knew a little about type setting but couldn’t figure out how to define types within Tables for a long time, great lesson. Thank you very much!
Only problem I have is how would I get these types globally recognized by server and client scripts?
I found out one of the ways via the Luau documentation (export keyword)
I managed to use this post, coolalex1835’s + 7z99’s reply and the Luau documentation OP linked to construct a rudimentary custom type called “Command” based off of CMDR. It’s a bit confusing to learn this stuff though.
Custom Type - Command
Type Definition Script (ServerScriptService → ModuleScript)
-- Type Definitions
-- Export this type with the Module Script so that we can access it. Not *necessary* but useful just in case
export type executable = (player : Player | nil, args : {string}) -> boolean
export type command = {
Name : string,
HasMetatableAccess : boolean | nil,
Exec : executable,
metatable : {any} | nil
} | nil
-- Type Constructors
local Command = {}
Command.New = function(name : string, metatableAccess : boolean, Exec : executable, metatable : {} | nil) : command
local c : command = {}
c.Name = name
c.HasMetatableAccess = metatableAccess
c.Exec = Exec
c.metatable = metatable
return c
end
return Command
Example Command using the Type Definition:
-- Require the Command Definition
local c = require(script.Parent.Command)
-- Create an Execute Function (that follows the Executable type)
function Execute(p : Player | nil, args : {string} | nil) : boolean
print(args)
return true
end
-- Construct and Return the command
return c.New("test", false, Execute, {})
Third Script to create and execute the command:
local c = require(script.Parent.TestCommand)
c.Exec(nil, {"does", "this", "work", "yet???"}) -- will print the table.
Something else to note which I found really cool is that type checking skills also transfer to the Swift Programming Language that was developed by Apple (doing a uni module with it included). This tutorial has been an invaluable tool that has taken me between several programming languages and helped me throughout my time at university so far.
--!strict
local Value : any = 5
print(Value + 1) -- This will shout at us because we have Value set to any, this means its not sure if this should be possible or not, this is where assertion comes in:
print((Value::number) + 1) -- This will work because we asserted that this would be possible, via setting its type to number.
-- Remember! assertion only applys for that operation if I were to now do
print(Value + 1) -- It would still shout at us