Type checking for beginners!

It has been out of beta for a couple of months already!
Regarding the type checking concept, every OOP oriented language is technically type checked (and they are also typed), since when declaring a variable you specify what type it is (and if you don’t specify, it will only accept the type it has been initialized with). And languages that are un or weakly typed, such as JavaScript, have typed versions, as TypeScript.
I think Luau type checking overall teaches you about typed languages in general (including Swift)!

4 Likes

i might be asking this to the wrong person but any idea why we can’t type check functions?

2 Likes

Well, what would you check them for? Can you elaborate?

2 Likes

I don’t fully get what you’re talking about? But if I understood you correctly, have you read point 5 “Type checking in functions”?

2 Likes

so, i’m kinda new to this type checking stuff but let’s say i’m trying to have a function return a function that returns a string or something

local foo = function(thing: string): function --<
	return function(otherThing: string): string
		print(thing, otherThing)
		return ''..thing..otherThing..''
	end
end

or maybe i’m trying to make an array that only takes functions:

type FunctionArray = {[number]: function} --<

or you have already have said array and it’s full of functions and you want to activate (for lack of a better term (or rather the right term lol)) all of them via pairs loop

for index: string | number, fn: function in pairs(functions) do --<
	fn()
end

just wanted to know why it can’t just work like that?

2 Likes

function can’t be used as a type because it is the keyword used for declaring functions. The interpreter’s going to scream at you if you try to do so:
image
It’s telling you that it was expecting a type, but got a function instead (not the function type)
HOWEVER, it has been mentioned that you can have predefined function types, and then you don’t have to type check functions that have been noted with the type:

-- Between the () are the argument types, after -> is the return type
type fun = (string, number) -> string

local test: fun = function(testString, testNumber)
	return testString .. testNumber
end

However, I didn’t include it in the topic because it doesn’t really work…


But nonetheless you can use that to type check function types.

17 Likes

Bumping the topic as I updated it with more information and the brand new assertion operator ::!

4 Likes

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)

3 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