Conversation
|
|
||
| Within a class block, two declarations are allowed: Fields and methods. | ||
|
|
||
| Fields are introduced with the new `public` keyword. |
There was a problem hiding this comment.
Understand the need for a new keyword but public strongly suggests we are going to do private therefore should that be included as part of the RFC? (Just the what, not the how)
There was a problem hiding this comment.
I suppose I could have done that, but I decided to leave it out because private has some very finnicky and complicated nuances to it with respect to how it behaves in code that lacks annotations.
There will be a followup RFC for private fields and methods.
There was a problem hiding this comment.
I don't think we need to explain how it works just that we intend to do it (and we'll figure out how later)
|
|
||
| The action of a class definition statement is to allocate the class object, define its functions and properties, and freeze it. Consequently, a class cannot be instantiated before this statement is executed. | ||
|
|
||
| We do, however, *hoist* the class identifier's binding to the top of the script so that it can be referred to within functions or classes that lexically appear before the class definition. This makes it easy and straightforward for developers to write classes or functions that mutually refer to one another. |
There was a problem hiding this comment.
Class hoisting implies the following code is valid:
local function getFoo()
local f = Multiple.new(2)
return f
end
print(getFoo()) -- ???
local function getData(factor: number)
return 123 * factor
end
class Multiple
public factor: number
function length(self)
return getData(factor)
end
endWhat is the expected behavior where getFoo() is called? A runtime error?
There was a problem hiding this comment.
Yeah. It's a runtime error. Multiple will have the value nil if getFoo is invoked before the class statement is evaluated. The RFC describes this a little bit further into the document.
There was a problem hiding this comment.
I am completely against introducing any hoisting to the language, with this case being a perfect example of why.
If someone needs to reference a class before it is defined, they should use a forward-declared variable as a surrogate to explicitly show that they are using a token that may or may not be defined yet.
There was a problem hiding this comment.
Also, the presence of hoisting seems to be part of the reason why class definitions aren't allowed outside the top scope, since hoisting scoped variables can get messy. I would wager everyone would prefer to have scoped classes over class hoisting if they had to choose between the two.
There was a problem hiding this comment.
Without hoisting, there is no way at all to write two classes that mutually refer to one another.
The top-level constraint is about establishing that there is exactly on instance of every method of every class. This makes it significantly easier for us to do static method dispatch.
I'll update the RFC to clarify this. Thanks!
There was a problem hiding this comment.
There's nothing wrong here. Classes as a statement has zero code to execute outside of the initialization. Fields do not have an initializer. Just don't let functions or locals reference any classes that are defined later.
local function foo()
return Foo.new()
-- ^^^ `Foo` is not bound in scope here
end
class Foo -- now `Foo` is bound in scope
bar: Bar
function new()
return Foo { bar = Bar { x = 5 } }
end
end
print(Foo.new().bar.x) -- prints 5. It's fine.
class Bar -- now `Bar` is bound in scope
x: number
end
-- ...the rest of the module...The right way to do the hoisting is by doing something like this:
class <unutterable-1>
bar: <unutterable-2>
function new()
return <unutterable-1> { bar = <unutterable-2> { x = 5 } }
end
end
class <unutterable-2>
x: number
end
local function foo()
return Foo.new()
-- ^^^ `Foo` is not bound in scope here
end
const Foo = <unutterable-1>
print(Foo.new().bar.x) -- prints 5. It's fine.
const Bar = <unutterable-2>That is, what you do is group all classes into a mutually dependent block that can reference each other unconditionally, and then and only then do you incrementally bring the name of the individual class into scope.
The reason why hoisting in JS gets so much hate is because it relies on TDZ which cannot be analyzed at compile time, that's literally why TDZ exists at runtime. Here, we can warn when attempting to reference Foo that is not yet bound in scope. You have a warning, so this isn't hoisting in the same sense as JS hoisting.
There was a problem hiding this comment.
That said, the RFC as is does phrase it like the solution is either-or: 1. no way for two classes to be defined out-of-order or mutually dependent in some way, or 2. do TDZ and say all class identifiers are hoisted to the top.
The solution I suggested avoids this problem, as long as classes do not have any initializers.
There was a problem hiding this comment.
Sigh. Flip the direction. Functions in class definitions can capture locals. TDZ.
class Bar
function run_foo()
Foo.print_x()
end
end
Bar.run_foo()
local x = 5
class Foo
function print_x()
print(x)
end
endThere was a problem hiding this comment.
What you're describing is an explicit phrasing of exactly what the RFC proposes except that we lose the constness of the class object. Prior to the definition of ClassB, its name is in scope but has the value nil.
It's not the best but I don't see a viable alternative.
I also don't know if it's really all that bad: It only arises when code is interleaving class definitions and executing imperative actions at the module scope.
There was a problem hiding this comment.
...So class variables are effectively globals?
If that's the case, how do shadowing and global variables behave with class definitions?
local A = 1 -- shadows A?
print(A) -- 1 or nil?
class A end
print(A) -- class A?
B = 2 -- global variable or parse error from reassignment?
print(B) -- 2 or nil?
class B end
print(getfenv()["B"]) -- 2?Or what if the module returns early before a class is initialized?
class A
function f()
return B {} -- 50/50 chance always nil?
end
end
if math.random(0, 1) == 1 then return end
class B endEven if a set of consistent rules can be made, it just seems like a lot of footguns are going to be caused by this.
|
|
||
| Reading or writing a nonexistent class property raises an exception. This makes it easy to disambiguate between a nonexistent property and a property whose value is nil. | ||
|
|
||
| The builtin `type()` and `typeof()` functions return `"object"` for any class instance. We chose this over having them return the class name because class names do not have to be globally unique (they must only unique within a single module) and because we do not want to make it possible for classes to impersonate other types. |
There was a problem hiding this comment.
I've always interpreted type to be the safe one that tells me the true type and typeof to be the one that might lie to me about the actual type. Any reason we wouldn't also do that here? Alternatively, what if typeof returned as a second argument, the pointer to the class?
There was a problem hiding this comment.
The main problem I want to really address is that type and typeof both work with strings.
Class names aren't required to be globally unique, so you could get wedged into a bad situation if a class happens to be unfortunately named.
instanceof is designed to solve this problem by working with class objects directly.
There was a problem hiding this comment.
I'd like to avoid code where I am mixing typeof, instanceof, and getmetatable (for table-based classes). Maybe it's not actually that bad in practice - but it would be nice to have a single method that tells me about somethings type.
Additionally, it can be nice to use these methods to 'inspect' the type. With instanceof I must enumerate all possible types the object can be. Sometimes I might want to write:
local supportedTypes = {
[FooClass] = true,
[BarClass] = true,
[BazClass] = false,
}
-- Version I'd like to write
function isSupportedType(object)
local T = typeof(object) -- May not be the exact statement you'd write
return supportedTypes[T]
end
-- Version I'd need to write with current proposal
function isSupportedType(object)
for T, supported in supportedTypes do
if instanceof(object, T) then
return supported
end
end
end
There was a problem hiding this comment.
I've always interpreted
typeto be the safe one that tells me the true type andtypeofto be the one that might lie to me about the actual type.
This is incorrect, neither type nor typeof can lie about the type name for performance and embedder sandboxing.
There was a problem hiding this comment.
Also, let's be honest. I don't think the code in the example is particularly great. Just write the return instanceof(o, FooClass) or instanceof(o, BarClass) or .... Dumb code is good code.
|
|
||
| ```luau | ||
| class Point | ||
| public x: number |
There was a problem hiding this comment.
Is there a way for me to define a static member such as Point.zero?
There was a problem hiding this comment.
Not yet. It's a good idea for an extension though.
|
The existence of a new VM type raises a question for the type checker. Obviously, I imagine the use would mostly be for inverting type requirements to accept anything that wasn't an object (for e.g. serializing data) which might be niche but it's still worth considering in my opinion. |
| local n = pcall(SomeClass.getName, someClassInstance) | ||
| ``` | ||
|
|
||
| To construct an instance of a class, call the class object as though it were a function. It accepts a single argument: a table that contains initial values for all the fields. |
There was a problem hiding this comment.
Are we going to optimize calls in the form Class { Field = "Blah" } (also with parens) so that they don't construct a table unnecessarily?
There was a problem hiding this comment.
Not necessarily for V1, but it's a good idea.
There was a problem hiding this comment.
This is a bit similar to the construction problem in the records RFC. It seems to me the only way you can ever enforce the zero cost constructor is to make the constructor private to the module at minimum. Otherwise if you export the class, every function calls with f { x1 = x1, ..., xn = xn } is potentially a class constructor or a normal function call, and breaks formatting, and also raises a weird question like what should these do:
local xy = {x = 1, y = 2}
apply(Point)(xy)
Point(xy)There was a problem hiding this comment.
The idea would be that the class constructor always exists as a real function you can call, but we provide a peephole optimization in the specific case of ConcreteClass { props ... } that inlines it and eliminates the temporary table.
Why limit Local, function, and type bindings can be defined in any scope, therefore it'd make sense to allow the same for classes. |
|
Certainly an interesting RFC so far. A couple of questions: Do we need access specifiers in Luau? Majority of the code written today do not need private fields, and since classes can capture up-values, do we truly need private fields? And since private fields are not going to be supported out of the box, it seems a little weird to include the Why create a whole new type in the VM? Couldn't a syntax sugar be implemented in place for table objects with a metatable instead? It seems unnecessary to me. Also, I believe inheritation will be important, as at the moment, you can already create classes with a lot of functionality and decent support for the type-checker (except for shared-self types, which should be coming eventually!), but when it comes to inheritation, things get rather difficult. |
|
@deviaze would you be able to give an example of this?
|
Sure! Here, the |
|
This doesn't seem to be stated in the RFC, but if |
|
What does the C API look like for classes? |
| ```luau | ||
| class Point | ||
| public x: number | ||
| public y |
There was a problem hiding this comment.
Can class fields have default values?
There was a problem hiding this comment.
Not yet. Sounds like a nice extension though.
|
Just had a quick skim, but I noticed |
|
|
||
| Also, frankly, its worth as a programming technique is controversial: the [Fragile Base Class Problem](https://en.wikipedia.org/wiki/Fragile_base_class) can cause significant harm to a project. | ||
|
|
||
| Lastly, Luau easily supports interface inheritance through its structural type system, so inheritance is judged to be lower priority. |
There was a problem hiding this comment.
Luau does not support interface inheritance through the structural type system for any type that has methods because method types are not expressible in the type system. Examples:
type interface = { foo: (self: interface) -> () }
type something = { value: number, foo: (self: something) -> () }
local function fn(obj: interface)
obj:foo()
end
local function create(): something
return {
value = 0,
foo = function(self: something)
print(self.value)
end,
}
end
fn(create()) -- type error!type interface = { foo: <self>(self: self) -> () }
type something = { value: number, foo: (self: something) -> () }
local function fn(obj: interface)
obj:foo()
end
local function create(): something
return {
value = 42,
foo = function(self: something)
print(self.value)
end,
}
end
fn(create()) -- type error!type interface = { foo: <self>(self: self) -> () }
local function fn(obj1: interface, obj2: interface)
obj1.foo(obj2) -- runtime error!
endThere are other avenues, but I believe I have explored them all, and it seems that there is no way to express interfaces with methods in Luau in a way where the type system gives no false positives and where it also catches common runtime errors. And this is without getting into generic interfaces.
|
|
||
| This is a really big feature that has lots of moving parts! | ||
|
|
||
| We need to introduce multiple new keywords: `class` and `public` to start and `private` later. |
There was a problem hiding this comment.
Probably worth noting that these are contextual keywords - they'll still be able to be used as identifiers.
|
|
||
| #### Class Objects | ||
|
|
||
| The action of evaluating a class definition statement introduces a *class object* in the module scope. A class object is a value that serves as a factory for instances of the class and as a namespace for any functions that are defined on the class. |
There was a problem hiding this comment.
Are class objects values? Can they be passed around? What is their type? What happens if you pass a class object into instanceof or type?
There was a problem hiding this comment.
Class objects are values that mostly behave the same as class instances.
They are not considered to be members of any class type, so instanceof will always return false.
This could change later if we wanted to introduce a type hierarchy a la Python.
There was a problem hiding this comment.
Should instanceof(MyClass, class) return true (assuming we introduced a class lib)
|
Looks pretty great, just some details that need to be ironed out in the RFC process! |
|
I'm on board insofar as polishing OOP in Luau, but some of the specifics of this specific proposal leave me feeling a bit iffy. The initial lack of support for private fields and methods is disappointing — understandable from the perspective of how the RFC describes the complications, but disappointing nonetheless. My concern with its omission pertains largely (if not entirely) to syntax and readability. Assuming users do not subscribe to prefixing bindings with There is "tension" between the syntax for fields and methods that we could honestly do better about. If functions are public by default, why do fields need to be declared with access modifiers? I'm also of the opinion that |
|
I don't think Even if a class Cat
field name: string
private field internal_id: number
function meow(self, text: string)
end
endIn my opinion, |
|
I find the rationale on implementing visibility modifiers being in the vein of "may as well because we have to introduce some keyword so it doesn't look weird" to be very weak. This just seems like a very bad way to decide on language features. Back on visibility modifiers, I think that if the goal is to avoid unwanted access, then there are better ways that already exist at the API or representation by putting that state behind handles, whether they are opaque references or simple indices into other structures. There does not need to be some keyword that adds a whole charade to how that data is being accessed that can ultimately be bypassed ostensibly. |
Roblox has, in the past, released APIs written in Luau using conventions like Something more robust is needed. I'm working on a separate RFC to get into the details of this.
We think there's a performance win to be had here: Class instances don't need the array part of a table, and we think that we can save some memory and improve cache locality by splitting the property hash table. The class object holds the set of keys in a particular class. The class instances just hold an array of values.
Shared-self unfortunately did not work out. I spent a couple of months trying to implement it, and it turned out to be a lot more brittle than I had hoped. I cover this briefly in the RFC. |
I still don't think this issue necessitates the implementation of access specifiers. Like I mentioned, an upvalue system could just be preferred instead. class A
function new()
local privateField = 0
local object = A()
object.getter = function()
return privateField
end
return object
end
endI believe a solution like this would be a better workaround. Of course, if private fields could be optimized somehow by the compiler, many people would be more okay with it. That aside, I do believe the reasoning as to why public and private exists is weak. No keyword looks great, and if we're still going to have keywords for fields, then it should be made with a keyword like "field".
That really sucks. Custom class modules perhaps would have benefitted from it. However, native classes in Luau sounds and works as a better option. I do look forward to potential optimizations, describing some of them would give this RFC a better foundation to work with. |
This restriction could be lifted someday. It's in place now just to keep things simple. |
This is probably okay to add at a later date. It requires some extra complexity in the VM, but it should be fine. |
The As for refinements using |
You're already able to do this. That's how Luau can infer the result of overloaded operators, e.g.
Iterating over a class is actually ill-defined at a fundamental level if fields can be private. In one scope, iterating over a class should result in public fields only, in another scope it should result in public and private fields... which is nonsense, I've never seen any language construct that depends on the scope in this way. Looks like JavaScript just says that iterating over the fields of a class with private fields do not ever yield the private fields, even if that private field is in scope.
Neither of these two are true at all. If you have The better solution is for the |
|
|
||
| Methods are introduced with the familiar `function` keyword. `public function f()` is also permitted. | ||
|
|
||
| If a method's first argument is named `self`, it can be invoked with the familiar `instance:method()` call syntax. Type annotations on the `self` parameter are not allowed. |
There was a problem hiding this comment.
This implies that every function in the class whose first argument is self has to check assert(class.isinstance(self, Self)).
There was a problem hiding this comment.
This implies that every function in the class whose first argument is
selfhas to checkassert(class.isinstance(self, Self)).
You shouldn't be able to call a method that takes self with the wrong type of object. Luau should probably throw a runtime error if that happens
There was a problem hiding this comment.
We've put a ton of thought into this kind of thing. If we walk down this path, it will be a more general typechecking mode where all annotations are validated at runtime rather than having one magic special case just for the type of self.
For this iteration, the type of self will always be statically resolved as that of the enclosing class, but it will be unchecked at runtime just like any other ordinary type annotation you might write.
There was a problem hiding this comment.
That's kind of disappointing, I think, because that means the compiler has to generate pessimistic branch for self in classes.
That feels like my expectation, as the use case would be for handling an unknown class, and if it is within the class itself then it isn't unknown. So there would be no reason to iterate for introspection, meaning no reason to ever include private fields.
Sorry should have been more specific, when I said "generalised" I meant "generic". i.e. an iterator which applies to all classes by default that do not have a customer iterator defined by |
| ## Summary | ||
|
|
||
| ```luau | ||
| class Point |
There was a problem hiding this comment.
It'd be nice to be able to write class MyClass: SomeSignature & SomeOtherSignature ... end as a way to make sure MyClass satisfies these requirements (or to unify with any metavariables in the class fields/methods).
|
Could we get more clarification on how the following piece of code would behave? class listener
--does this field count as a method?
--are dynamically assigned functions in objects forbidden?
--or... am i blind and this was clarified in the rfc?
public callback: (self) -> ()
function new(callback: (self) -> ())
return listener { callback = callback }
end
end |
There are no special restrictions on class fields that are functions. I think your code would be fine. |
|
Here's a crazy bikeshed idea: make class definitions mirror function definitions like Kotlin and require parentheses around the fields. This actually makes using class Point(
public x: number, -- private when no modifier, can introduce others like const and read
public y: number
)
-- can put public before functions with no ambiguities
public function getLength(self): number
return (self.x ^ 2 + self.y ^ 2) ^ 0.5
end
-- metamethods must be public
public function __add(self, other: Person): Person
return Point(self.x + other.x, self.y + other.y) -- alternate constructor ?
end
endTo me, this looks much more Luau-y and solves a few potential ambiguities. It also has obvious syntax extensions for generics and inheritance/mixins. class Tree<T>(nodes: {T})
end
class BinaryTree<T>(nodes: {T}): Tree<T>
endThe only catch is this syntax implies the constructor also mirrors a normal function call like |
This syntax actually reminds me a lot more of that awkward ability to create classes with functions in JavaScript. // From https://learn-js.org/en/Object_Oriented_JavaScript because its the first thing I thought of
function Person(firstName, lastName) { // why is this even a thing YOU HAVE A CLASS KEYWORD
// construct the object using the arguments
this.firstName = firstName;
this.lastName = lastName;
// a method which returns the full name
this.fullName = function() {
return this.firstName + " " + this.lastName;
}
}
const myPerson = new Person("John", "Smith");
console.log(myPerson.fullName()); // outputs "John Smith"I'd much rather have classes look like a regular block as proposed by the RFC. It not only fits with Luau's/Lua's syntax much more, but it also looks cleaner than the Kotlin-like example in your comment. class Car(public model: string, public make: string, public year: number, public speed: number, public engineType: string)I doubt people would want to write code like this. |
|
@InfraredGodYT Here's my rationale for the parentheses:
class Example
public private
function f() end -- private function or field named private?
public x =
public y -- public x = public; y?
function g() end
public z = g() -- is this legal?
end
For your example, why can't you just indent the fields like you normally would with a long function definition or table? This is one of the benefits of Lua(u) being whitespace insensitive. class Car(
public model: string,
public make: string,
public year: number,
public speed: number,
public engineType: string
)
end |
|
This is a welcome direction. The metatable OOP pattern has been one of the sharpest paper cuts in the language for years, and the less-than-hoped-for delivery of shared-self types makes a first-class construct the obvious path forward. Most of the feedback in this thread has focused on individual design decisions, and I think that's all been productive. What I want to step back and ask about is the layering strategy, because I think it's where the biggest risk lives. This RFC defers private fields, generic classes, inheritance, default values, static members, and scoped classes. Each of those is reasonable to defer on its own. But collectively, they mean that a large number of the decisions being baked in now are load-bearing predictions about designs that haven't been worked out yet. The Luau has historically been good about this kind of caution. zeux's Records RFC arguably erred too far toward minimalism, but its instinct was sound: commit to as little as possible in v1 so that extensions don't have to work around early decisions. I think this RFC would benefit from the same instinct in a few places. Concretely, choosing Then again, I am vastly outside my comfort zone here; if any of this feedback is unsound or unwarranted noise, please take it with a large grain of salt. I bear no experience in such low-level engineering, so I only hope to offer my two cents on the development strategy of the RFC as it goes past its MVP stage. |
|
I realized something that's going to be extremely annoying. In Lua, tables whose fields are For example, if a name does not have a middle component, that's still a valid class Name
first: string,
middle: string | {string} | nil,
last: string,
function __tostring(self)
local s = self.first
s ..= if self.middle == nil then ""
elseif typeof(self.middle) == "string" then " " .. self.middle
elseif typeof(self.middle) == "table" then " " .. table.concat(self.middle, " ")
else absurd(self.middle)
return s .. " " .. self.last
end
end
local names = {
{ first = "Foo", middle = "Bar", last = "Baz" }, -- valid
{ first = "John", middle = nil, last = "Doe" }, -- valid
{ first = "Jane", last = "Doe" }, -- invalid?
}
for _, name in names do
print(tostring(Name(name)))
endWhat's your plan on this? |
|
I think that there are many forward-looking things, and it may be best to delay general release until classes are considered to be in a complete state, so that things can be changed as needed without introducing breaking changes. |
I do agree with the argument that |
|
I think in regards to Adapting |
|
Wanted to follow up on the field keyword question specifically. The RFC dismissed local because “fields are not locals, they do not inhabit the stack,” but I think that reasoning optimizes for theoretical correctness over practical familiarity. class Cat
local name: string
local lives: number
function meow(self)
print(self.name .. " says meow")
end
endNo new keywords at all; it composes with const if that ever applies to fields, and when private arrives, |
|
|
||
| ```luau | ||
| local class: { | ||
| isinstance: (o: unknown, C: class) -> boolean, |
There was a problem hiding this comment.
Let's take this one out? It's backward compatible to retrofit the o is C notation as long as the expression on the right after is is an identifier-starting token, and you even get a guarantee that o is C could lower to one instruction that:
- If
ois an object, load the class ofo. - If the class of
ois equal toC, jump to the consequent (no need to assert whetherCis a class). - Otherwise,
Ccould possibly be a non-class. If it is not a class, throw an error. - Otherwise, jump to the alternative.
This would mean the compiler can avoid generating the pessimistic branch where class is not the built-in class library and the assert function is not the built-in assert function. Then something like this is as tight as possible.
export function foo(o: MyClass)
if not o is MyClass then -- maybe we should add `o is not MyClass`?
-- adding a `return` here to prevent a join in the codegen's CFG
return error(`expected {MyClass} but got {typeof(o)}`)
end
-- now the compiler knows it can generate specialized code for `o`
return o.foo
end
export class MyClass
foo: number,
bar: number,
end|
I agree again that In my opinion, matching this new system to existing language aesthetics would be a positive! Most everything else in Luau is "public-like" so that should be the default "quickest to achieve". Then, |
Agreed, they should be behind a FFlag that has to be manually enabled at first. Alongside a studio beta for Roblox, rather than just pushing them into production immediately after they're implemented according to this RFC. |
We've talked about field declaration syntax internally a lot. This is one of the things we talked about. We decided not to do it because it's a little bit too off-the-beaten-path and doesn't have much of an upside to the current syntax.
I take your point, but you can see that classes has the potential to become a massive feature! We need to strike a balance: On one hand, we need to constrain the discussion space so that we can productively make decisions. On the other, the final feature really should look like a unified whole rather than the sum of a series of hedged bets. On the topic of
There's no solution here. If you wish to initialize a nilable field with nil, omitting the key has to be a valid way to do so.
Not to worry. I think so too. This one needs a bit of hands-on-keyboard time to surface practical issues that might not be immediately apparent. |
|
The only reason I'm adamant about the parentheses is because unlike conventional OO languages, the fields of Luau classes are the constructor and have to be initialized. The standard syntax for fields doesn't reflect that since most languages like C# or JavaScript just treat their fields as zero-initialized/undefined until the constructor assigns a new value to them. Luau classes don't have the same mechanics, so the syntax is just a bad fit. Mirroring a function call with the prior art of "default constructors" makes it very clear that fields are also the constructor and have to be initialized with something. It also neatly separates the instance fields from the class body, which opens up the possibility of adding other constructs like static fields to the class body easily: class Person(name: string, id: number)
local currentId = 0 -- static field
function new(name: string)
currentId += 1
return Person(name, currentId)
end
endI'd like to see how static fields could be done in a cleaner way than this. |
|
@alexmccord Also, just saying that making the constructor a normal function call solves your issue with missing keys since function arity isn't affected by nil: local x = Name("Foo", "Bar", "Baz") -- valid, 3 args
local y = Name("John", nil, "Doe") -- valid, 3 args
local z = Name("Jane", "Doe") -- invalid, 2 args |
|
I like the idea behind having fields "like function parameters" but do not like ordered parameters for construction. I have found these to be quite fragile when working with classes with a large number of fields, and always resulted in me creating a "from_table" static method. It would be interesting syntax, possibly to extend to functions, where they can be defined using If that sounds like a useful extension then we could at first use Mostly just food for thought. function foo{ name: string, reps: number | nil }
for i = 1, reps or 1 do
print("Hi " .. name)
end
end
class Cat{ name: string }
function speak(self)
print(self.name)
end
end
foo{ name = "Alice", reps = 5 }
local bob = Cat{ name = "Bob" } |
|
I feel like () this should be the case class Cat( name: string ) function foo( name: string, reps: number | nil ) And luau do a python with named variables foo( name = "Alice", reps = 5 ) |
| * `__newindex` | ||
| * `__mode` | ||
| * `__metatable` | ||
| * `__type` |
There was a problem hiding this comment.
Would it make sense to reserve all method names starting with a double underscore (__)? That would ensure that if we introduce new metamethods in the future they can't possibly conflict with user code.
Rendered.