I’ve got some ideas for handling this automatically. In the meantime, a ‘forward declaration’ will do the trick:
local fib: (number) => number
I’ve got some ideas for handling this automatically. In the meantime, a ‘forward declaration’ will do the trick:
local fib: (number) => number
Setting the parameters of UDim2.new()
using numbers will show a warn on the script editor, this doesn’t happen if UDim.new()
is used instead.
What are the plans for the default behavior of type inference for unannotated variables, especially with strict mode enabled?
My OOP code seems to have much less false positives for nonstrict mode now; however, it seems to be doing a lot of type inference guesswork on unannotated variables, to a degree that is very confusing and unwanted. I would much rather prefer having any
type or {[any]: any}
as the default typing for unannotated variables, and then have the script analysis complain about how “this variable has any
type” in non-strict mode. This is similar to the way typescript works.
TypeScript:
This is so elegant because you always know what to expect. Type inference only happens at the point of declaring a variable. If you declare a variable with no annotation or initialization, it defaults to any
type and complains. If you declare a variable as a table with not fields, it makes that inference once and then complains if you add fields that weren’t specified before. It may be a pain to specify everything explicitly, and TypeScript’s classes aid in simultaneous value/type declaration. However, the alternative (which seems to be the approach being pursued right now) is far, far messier and unpredictable.
Here’s an example piece of code where type inference has gone very crazy (this is in strict mode only; in nonstrict mode, the compiler doesn’t complain about much at all):
The above code is a method in a class called EntityReplicationClient. This class is constructed with an untyped domain
field, among other fields (this should be typed to another parent class, but since we don’t yet have type sharing across modules, I must leave this to type inference).
This emits a warning for an entire block of code, since the script analysis seems to be really confused about the gargantuan types it’s trying to infer. In similar TypeScript code, self.domain
would be typed as any
when there are no type annotations.
Again, this is all type annotation-free code. self.domain
is therefore typed through inference. in the first half of the code, self.domain
is inferred to have a method called ForAllEntitiesWithComponent
, which is typed as (string, {RegisterEntityLink: (???, ???) => void}) => void
self.domain:ForAllEntitiesWithComponent(
'SN_REPLICATED_ENTITY_LINKER',
function(_, SN_REPLICATED_ENTITY_LINKER)
SN_REPLICATED_ENTITY_LINKER:RegisterEntityLink(serverEntity, clientEntity)
end
)
Then, in the second half of the code, ForAllEntitiesWithComponent
is called again; now that its type has been inferred, a new function is passed in, calling a second method on SN_REPLICATED_ENTITY_LINKER
, which has indirectly been inferred to be typed {RegisterEntityLink: (???, ???) => void}
, and is therefore missing the second method ''GetClientEntity"
What’s odd in this example too is the direction of type relation; it’s not getting mad that ''GetClientEntity" is not a property of {RegisterEntityLink: (???, ???) => void}
, but rather that “RegisterEntityLink” is missing from {GetClientEntity: (???) => ???}
, so that it’s doing type inference separately on the second call to ‘ForAllEntitiesWithComponent’, and then complaining that the inferred type on this call to ‘ForAllEntitiesWithComponent’ doesn’t match the inferred type from the last call to ‘ForAllEntitiesWithComponent’
It’s all very convoluted and nonsensical.
I don’t think the type inference needs to do this much work attempting to guess how the user wanted to type their code; that should be left to the user to decide how they want to type their code, and in my opinion it’s a lot simpler, straightforward, and safer to type things as any
or {[any]: any}
by default, and then only emit a warning in strict mode for cases where any
is inferred due to a lack of type annotations.
As far as I can expect, if Typed Luau is shipped with exportable types between modules, my OOP code is going to look a lot like C++ OOP code—each class module would have a header module nested underneath it, and this header would include all the necessary typings for objects of a certain class, which can be circularly required among parent/child classes (the parent implementation can reference the child’s type, and vice-versa). Although this can be a pain to write typings and implementation in two different places, if we aren’t given a TypeScript-like class syntax with typed luau, it’s going to be difficult to rely on type inference in more complex OOP structures like my example.
Other than these few thread issues, I look forward to the advantages of Lua Strict.
Why is nil, number, string, and boolean being called Primitive Shouldn’t it be native? (Just feel Primitive is a little derogatory.)
“Nonstrict mode is way more permissive than we’d like.” aka Nonstrict is Nonstrict?
It would be nicer if you just called this OOP instead of STRICT LUA PROGRAMMING.
Because a primitive type is standard terminology across programming languages. Tables and userdata are complex data types, but they are also native.
They want to make nonstrict stricter, which doesn’t mean making it as strict as strict.
This has nothing to do with OOP.
Regarding your first point, the choice of wording is precise as to follow the industry standard.
As Raviates so eloquently put it, the widely accepted standard terminology for built-in data types is Primitives. The reason behind this is that they are in fact the primitive building blocks to allow you to create your own, more complex data types; which as an OOP user you have most likely done.
Moreover, let me indicate to you that despite the clear advantages that these updates present to OOP users, they are not themselves OOP. They do not aid in the creation or management of objects, they merely allow you to strong-type variables. While this latter function presents clear benefits to OOP users, it is not related in any way, and is in fact a part of many languages that cannot follow OOP methodologies.
table.insert(Table, #Table + 1, Value)
I got this error: Expected '(', '{' or <string>, got 'table'
. Why? is this a bug or something else?
Can you share the previous statement? My best guess is that it’s what’s tripping things up.
function ReplySystem.AddCameraCFrame(Name, Value)
local PIT = #ReplySystemArray.CFrames.CameraCFrames: number
return table.insert(ReplySystemArray.CFrames.CameraCFrames, PIT + 1, {Value,MaxTime,"Name:"..Name})
end
if i dont use the :number
then the game warn me: W000: Unknow symbol: 'ReplySystemArray'
, but:
type ReplySystemArray = {__index: ReplySystemArray, CFrames: {CameraCFrames: {}, ObjectsCFrames: {}}, Objects: {}}
Then, why? Why it warn me and why it say me it was a error?
Edit: Ok, i have understand why it say me the error, but why it warn me?
Got it. There are a few things going on here.
First, when you create a type with the type
keyword, you aren’t creating a table. You aren’t creating anything, in fact. You’re just defining a name that refers to the shape of a hypothetical table.
Second, the annotation goes after the variable you are creating, not the expression that you’re assigning to it.
local ReplySystemArray = {}
function ReplySystem.AddCameraCFrame(Name, Value)
local PIT: number = #ReplySystemArray.CFrames.CameraCFrames
-- ^ Annotation goes here
return table.insert(ReplySystemArray.CFrames.CameraCFrames, PIT + 1, {Value,MaxTime,"Name:"..Name})
end
OMG, it worked, Thank you very much!!!
But then why we need the type keyword?
type
is used for “custom table types” (and there are other uses, read OP).
For example, if you wanted a Vector1
type for whatever reason, do it!
type Vector1 = { X: number }
-- This means Vector1 is a new type, and the table requires an X field that is a number!
local v1: Vector1 = { X = 5 }
v1
points to a table that requires an X
field. It must contain a number.
It’s clearly following in the footsteps of an Object-Oriented Programming language, I could assume the goal is to eventually make a class hierarchy (like polymorphism).
What’s the purpose of making Nonstrct stricter? I don’t see the purpose other than possibly making the scripts more optimized by giving more precise code.
Thanks for clarifying my point.
Unsure if this has been reported, but the following code gives a cryptic error:
--!strict
type A = { foo: number }
type B = { foo: number }
function foo(x: A | B)
return x.foo
end
…gives the following warning on x.foo
:
Would you be able to have 2 functions of the same name, but take different types? Much like how C++ class methods work, then error if there is no function of that name with the correct type syntax?
That’s actually called function overloading and it would great if Roblox added it to luau.
https://en.wikibooks.org/wiki/Computer_Programming/Function_overloading
This has a name; Overloading, and yes, i would it add but i think that it will be out with the Generics. I know this from Minecraft Modding (the reason why i am not very active in the community) and it exist in many Programming languages, like Java. I only want to know how we manually use it.
@JoelBrd @Eternalove_fan32 @vivivio
I actually asked this a while back, and I got this reply:
Ok, dont know if Roblox will ever add it, but can the Roblox team add the Ternary Operator system? It would save us a lot of time by simply write:
local string = "Its a example"
local String = "Its a example"
string == String ? "Roblox will add it!" : "Nope, it will never be true"
And not:
local string = "Its a e."
local String = "Its a e."
local function check()
if string == String then
return true
else
return false
end
end