Type checking for beginners!

First off:

You duplicated the section “9”

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?

6 Likes

Fixed!


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! (^▽^)

13 Likes

A very good tutorial, really explains type checking very well for beginners.

3 Likes

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.

Thank you for the writeup!

9 Likes

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.

2 Likes

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:
obraz

(and it infers the return type from the body of the function, instead of the type too)

Oh yeah and I need to update this tutorial :sweat_smile:

2 Likes

I found a way and its quite hacky and might not be needed but you can by using typeof(function() end)

4 Likes

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,
	}
)

Thank you for any help!

2 Likes

You forgot about the self parameter in methods.

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 ."

2 Likes

He didn’t forget anything. That’s not a class and it’s not called with self.

3 Likes

You can actually fix it by adding a ? at the end, or by doing | nil after number


to solve that,

number | nil looks a bit uglier imo but in essence, it’s the same thing

Furthermore, we can also return multiple arguments by encasing the return with parentheses



Also little PSA but you can use typechecking with metatables as well by using the typeof() function:



Only issue is that it’s a bit useless, I can’t really think of a use case other than this:

Now, we can use this to create some partConstructor type:


Only downside is that the __newindex metamethod doesn’t fire.
Screen Shot 2021-11-24 at 10.24.47 PM

5 Likes

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
4 Likes

Big fan of this thread

(types passed into function) -> (types returned by function) can be used to infer function types, for example

image

3 Likes

There is another way to check if a thread is a function that is easier to understand and is shorter:

local IsFunction = string.split(tostring(YourThread),' ')[1] == 'function:'

Edit (31 may): completely wrong, ()->() is far easier.

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

1 Like

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!

1 Like


This does work well.

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.

1 Like

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.

Thanks, Zamd and contributors :heart:.

For those wondering :: asserts a type,

This means in this example:

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

Hope this cleared it up

1 Like

hello, good afternoon, how can I program that when the player makes 10 kills, he can be given the emblem of 10 kills.