-
Notifications
You must be signed in to change notification settings - Fork 111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Standardized class model? #97
Comments
This is a big question!
I tend to lean towards "good idea" but I also realize that it could turn a lot of people off to the language (and maybe another lot of people on?). The reason why I think about it is not so much to get the full kitchen sink of OOP features into the language, but for two main reasons:
I remember @mascarenhas suggested once adding a class model to Typed Lua (or Titan?) with "pluggable backends" so that they could emit code for different Lua OOP models — while the idea is intriguing I think it is overkill for Teal. |
TypeScript did get classes years before ES6 was finalized and classes officially got into JavaScript, but they had the fig leaf that the proposal was already going through standardization, ES6 just took a loooong time. 😄 I have always been in favor of baking in classes and interfaces in the language definition, and Typed Lua would have them if we kept going; all the alternatives PL people have come up with to model the same kinds of concepts that OO models are more complex. 😄 I was looking at the docs, does this work, and what does it compile to:
Is the Also, does the special-casing of casting a table constructor to a record also work for return types? Because if that is the case this gives a way to do constructors without special syntax:
Implementation inheritance is overrated, and given that with the standard way of doing inheritance with Lua metatables method resolution is O(length of inheritance chain) I don't believe inheritance was ever popular in Lua. 😄 |
By the way, modeling the
The type of
In Typed Lua we had a |
To this: local Point = {}
function Point:move(dx: number, dy: number)
self.x = self.x + dx
self.y = self.y + dy
end
local p = { x = 100, y = 200 } So for now you need to add the metatable plumbing by hand for the above so that it works. (Right now there is a regression in the code but it's a silly bug which should be fixed quick.)
Yes, it does!
Noted! The above doesn't even work because we can't do
Yeah, I'm currently parsing functions and methods ("record functions") as different things (i.e., I chose to not desugar |
A simple encoding is to use the "class" as the metatable for the instances, and have
The wart with this encoding is that the same namespace you use for your methods is polluted with your constructors and with your metamethods, but that is the price to pay for having the generated code be more like what a programmer would write by hand... I am sure people would complain about the extra cost of record instantiation, but I would just make all records have the reified type as a metatable, as that will give you runtime type checks. Of course use of |
This is sort of what users do by hand already, but not something I'd plug in every record, of course.
I don't think I can because of interop with Lua (existing Lua libraries returns tables which I want to type as records and those don't set the metatable), but when thinking about adding explicit OOP this was the direction I was thinking about (so records would not always get an automatic metatable, but objects would). |
I dont know if this is a good idea. OOP in lua is so ad hoc and dynamic that I think this would easily feel restrictive to some people. The Typescript to JS situation with classes was different since -- if I recall correctly -- there was already a standard pattern with function prototypes and then the |
I would never prevent people to manipulate metatables explicitly like they do today. The only issue is that fully-manual OOP is very dynamic, which means the compiler has no information about it — so when you do the metatable plumbing by hand you also need to add typecast plumbing using (In other words: if doing OOP by hand in Lua is verbose, doing it by hand in Teal is currently even more verbose.)
This is the general direction I've been leaning lately (not discarding OOP in the roadmap but thinking about adding something to records first). This thread has a lot of food for thought, lots of great input!
Well, yes, not as a standard part of Lua but, as you said...
...there are de facto patterns that are popular out there, which have some common baseline. Even the PiL book teaches these patterns. So, just like |
Yeah I don't think this would do any harm either 👍 |
Trying to make the compiler understand and type simple metatable patterns for OO was also something we tried with Typed Lua, the problem is that, while in Lua you can abstract away the metatable boilerplate (and this is what some of the "class systems" do) you cannot do that with a static type system. In Lua you can easily do:
But in Teal (as in Typed Lua) with setmetatable hacks you are stuck with the boilerplate, unless you decide to go the macro route and open another huge can of worms. 😄 |
Ah, I did not mean that. The compiler won't try to understand it. To use metatables you have to put typecasts by hand everywhere (that's why I said doing it in Teal it's even more boilerplate than in Lua). The path I'm considering is to just wrap one common metatable pattern as a language construct ("class", "object", whatever), which the compiler will be able to understand and that's it. If you want to use metatables explicitly in other creative ways, then you're on your own. |
What about instead of OOP, we had something like embedding in Go? For example: type Discount struct {
percent float32
startTime uint64
endTime uint64
}
type PremiumDiscount struct {
Discount // Embedded
additional float32
} |
is tl will support interface grammar like c# ? |
I've hacked together a branch that allows for record embedding. While this isn't a fully integrated solution to OOP (as it doesn't hook into metatables at all), it does allow for composition of types similar to Go with its embedding or the feel of C with anonymous structs inside of structs. Currently the branch type checks the basics of embedding:
But has some trouble resolving generics
works fine but
doesn't. Internally, this basically just lets the record with the embedding be substituted for any of its embeddings More complex things like the semantics of how nesting and embedding records can be worked out later, but I'd like feedback before working further. Additionally, I think this could replace how array-records work and introduce map-records both as just records with arrays and maps embedded. This was partially inspired by attempting to write some type defs for luv, where all the handle types inherit some base methods and a simple copy-paste sort of method like embedding works nice for those definitions. |
@euclidianAce This is a really interesting experiment! This is really another step towards intersection types (as were function overloads in records and array-records). I'm starting to wonder if the compiler should just adopt intersection types internally for real, and expose special syntax for common cases* for better ergonomics (or PX (programmer experience), if we want to be cool :) ). (* and possibly restrict the ones that are hard/unfeasible to deal with given runtime constraints, as we've done with other features such as unions) |
While intersection types are not a direct solution to integrating metatables, I do think it would be super helpful for annotating existing OOP implementations. For example, the embedding branch only allows records to be embedded but that's just an if check. Embedding a function type could help emulate a |
is there any news on this? |
I was pointed in this direction to discuss the "metatype model" of Lua. I've written a pure-lua library inspired by teal to encapsulate what I think this could look like. IMO it would be great if teal (and others) used something similar to this, enabling runtime type checking for tests and also getting rid of many of the warts of lua (primarily debug formatting and table equality) https://github.com/civboot/civlua/blob/main/metaty/README.md My suggestion in #697 was that Teal could compile something like this:
Into Lua code using metaty (a.k.a Teal would depend on metaty or similar):
Note: metaty allows representing types in pure lua and enables (optional) type checking which is zero-cost when disabled. This would allow for incremental transition from Lua to teal. Also, enabling test-time type checking at runtime means that Lua code importing Teal could still get fail-fast type errors if/when they use the incorrect type: something that is very valuable IMO for the interop story. |
From: #692 > It took me a bit of digging the back history to try to remember why `.` and > `:` have separate handlers. Of course, in Lua, `:` is just syntactic sugar. In > Teal, however, we have in the language semantics separate concepts for > functions and record-functions/methods, because the latter are statically > bound to their records. > > For example, want to be able to detect that the programmer made a typo and a > method name is invalid, and that means that we need, at one point of the code, > to declare that the declarations of methods are done. So you can only do > `function my_record:mymethod` in the same scope where you created your record, > for instance. > > Internally, Teal does keep track if a function "is a method" (that is, if it > was declared with `:` or `.`; in more recent versions, we even check if a > function is method-like by having a first argument `self` with its own type). > This is to keep the door open for future enhancements to the type system, such > as interfaces (see this comment: > #97 (comment)). > > So, it's an explicit decision to not make `:` just a syntactic sugar for `.`, > like Lua.
From: #692 > It took me a bit of digging the back history to try to remember why `.` and > `:` have separate handlers. Of course, in Lua, `:` is just syntactic sugar. In > Teal, however, we have in the language semantics separate concepts for > functions and record-functions/methods, because the latter are statically > bound to their records. > > For example, want to be able to detect that the programmer made a typo and a > method name is invalid, and that means that we need, at one point of the code, > to declare that the declarations of methods are done. So you can only do > `function my_record:mymethod` in the same scope where you created your record, > for instance. > > Internally, Teal does keep track if a function "is a method" (that is, if it > was declared with `:` or `.`; in more recent versions, we even check if a > function is method-like by having a first argument `self` with its own type). > This is to keep the door open for future enhancements to the type system, such > as interfaces (see this comment: > #97 (comment)). > > So, it's an explicit decision to not make `:` just a syntactic sugar for `.`, > like Lua.
From: #692 > It took me a bit of digging the back history to try to remember why `.` and > `:` have separate handlers. Of course, in Lua, `:` is just syntactic sugar. In > Teal, however, we have in the language semantics separate concepts for > functions and record-functions/methods, because the latter are statically > bound to their records. > > For example, want to be able to detect that the programmer made a typo and a > method name is invalid, and that means that we need, at one point of the code, > to declare that the declarations of methods are done. So you can only do > `function my_record:mymethod` in the same scope where you created your record, > for instance. > > Internally, Teal does keep track if a function "is a method" (that is, if it > was declared with `:` or `.`; in more recent versions, we even check if a > function is method-like by having a first argument `self` with its own type). > This is to keep the door open for future enhancements to the type system, such > as interfaces (see this comment: > #97 (comment)). > > So, it's an explicit decision to not make `:` just a syntactic sugar for `.`, > like Lua.
From: #692 > It took me a bit of digging the back history to try to remember why `.` and > `:` have separate handlers. Of course, in Lua, `:` is just syntactic sugar. In > Teal, however, we have in the language semantics separate concepts for > functions and record-functions/methods, because the latter are statically > bound to their records. > > For example, want to be able to detect that the programmer made a typo and a > method name is invalid, and that means that we need, at one point of the code, > to declare that the declarations of methods are done. So you can only do > `function my_record:mymethod` in the same scope where you created your record, > for instance. > > Internally, Teal does keep track if a function "is a method" (that is, if it > was declared with `:` or `.`; in more recent versions, we even check if a > function is method-like by having a first argument `self` with its own type). > This is to keep the door open for future enhancements to the type system, such > as interfaces (see this comment: > #97 (comment)). > > So, it's an explicit decision to not make `:` just a syntactic sugar for `.`, > like Lua.
From: #692 > It took me a bit of digging the back history to try to remember why `.` and > `:` have separate handlers. Of course, in Lua, `:` is just syntactic sugar. In > Teal, however, we have in the language semantics separate concepts for > functions and record-functions/methods, because the latter are statically > bound to their records. > > For example, want to be able to detect that the programmer made a typo and a > method name is invalid, and that means that we need, at one point of the code, > to declare that the declarations of methods are done. So you can only do > `function my_record:mymethod` in the same scope where you created your record, > for instance. > > Internally, Teal does keep track if a function "is a method" (that is, if it > was declared with `:` or `.`; in more recent versions, we even check if a > function is method-like by having a first argument `self` with its own type). > This is to keep the door open for future enhancements to the type system, such > as interfaces (see this comment: > #97 (comment)). > > So, it's an explicit decision to not make `:` just a syntactic sugar for `.`, > like Lua.
Is there anything on this? The lack of even type intersection makes any form of class inheritance really weird to annotate |
@Hedwig7s There's a new |
This has been mentioned in #86 (comment).
I think it's a good idea (although Lua purists might not like it?)
But then, which features should Teal support? Inheritance? Access modifiers? Getter-setter properties? Interfaces?
The text was updated successfully, but these errors were encountered: