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”?
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?
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:
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.
Bumping the topic as I updated it with more information and the brand new assertion operator ::
!
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?
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! (^▽^)
A very good tutorial, really explains type checking very well for beginners.
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!
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)
Oh yeah and I need to update this tutorial
I found a way and its quite hacky and might not be needed but you can by using typeof(function() end)
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!
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 .
"
He didn’t forget anything. That’s not a class and it’s not called with self.
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.
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
Big fan of this thread
(types passed into function) -> (types returned by function)
can be used to infer function types, for example
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
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!