-
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
Functions that create and return new classes/records (OOP wrappers) #200
Comments
creates a new type named
A record is a type in the sense of There currently isn't a decent work around for this as OOP is kind of an open discussion/problem right now. Perhaps something could be hacked together with generic records but it would definitely feel clunky. |
I see, thanks for the information. In my opinion, I don't think that Thanks for your work. I'm really hoping this will end up as the definitive "Typescript for Lua" that I've been wanting for a long time. |
No problem 😄. Just to be clear, @hishamhm is the one who created Teal, I just contribute here and there, but he's definitely the one to thank as he's put in the brunt of the work. |
I hacked on an example of the kind of feature I'm thinking of. https://github.com/Ruin0x11/tl/tree/metaprogramming Basically I edited the compiler to have a new global function which takes a string identifier and a record, and returns a new type. The expressions containing the arguments to the This is a custom hook specifically designed for the OOP wrapper I use. It autogenerates a local ClassMetadata = record
name: string
typedef: Type
end
on_assign_method = function(self: Type, node: Node, fn_type: Type, a_type: function(t: Type):(Type), node_error: function(node: Node, msg: string, ...:Type):(Type))
local fn_name = node.name.tk
if fn_name == "init" then
local metadata = self.metadata as ClassMetadata
local name = metadata.name
local props_type = metadata.typedef
for k, field in pairs(props_type.fields) do
self.def.fields[k] = field
table.insert(self.def.field_order, k)
end
fn_type.args[1].type = self
fn_type.rets[1] = self
self.def.fields["new"] = a_type {
typename = "function",
typeargs = fn_type.typeargs,
args = fn_type.args,
rets = fn_type.rets,
is_method = true
}
table.insert(self.def.field_order, "new")
end
end After these changes, this program now typechecks: local fields = record
min: number
max: number
end
local IntGen = class.class("IntGen", fields)
function IntGen:init(min: number, max: number): number
self.min = min
self.max = max
end
function IntGen:pick(): number
return math.random(self.min, self.max)
end
local gen = IntGen:new()
local n = gen:pick()
print(n + n) Again, only a hack, but this is the kind of thing I'm thinking of having. It would be some kind of way of hooking into the compiler to tell it how to interpret a function call that returns a type, because there is a lot of variance between the different OOP wrappers available for Lua. New syntax would probably needed to declare the local ClassType = metatype
on_declare = function(self: table, name: string, fields: record) ... end
on_assign_method = function(self: table, node: tl.Node, fn_type: tl.Type) ... end
end
local class = record
class: function(name: string, field: record): ClassType
end |
@Ruin0x11 That is super interesting. Compile-time metaprogramming is a super powerful tool and might indeed be the right tool for the problem here. I gave your branch a first look and will continue to think about this! (On a bit of an offtopic note for this particular issue but still on the topic of language evolution, would appreciate your impressions on #194 !) |
Another thing I thought of recently: I think Lua is uncommon in programming languages in that it does not come with object-oriented programming support built-in. Even Javascript got its own I think it may be difficult to capture the specific quirks of every custom OOP system if people want Teal versions of those libraries. Some systems support mixins, but some don't. Some support inheritance or interfaces, and some don't. I was weird and added in a delegation system that hacks into I think the "metaprogram in your own Another option would to just leave all the old OOP systems behind and introduce Teal's one class system like Javascript did, and continue until something that absolutely cannot be represented with that class system is found, then iterate. I think JS succeeded in its class adoption because the syntax was an official extension, not an offshoot of an existing language like Teal. |
Maybe I am missing something obvious (I probably am as I tend to avoid OOP in lua) but... Does teal need to care on how a lua type managed to do its inheritance? If you just want to use an Admin type as a User, you don't, at least for as long as they come from the same library. So, for those cases, Teal can just pick a method and call it a day, for as long a .d.tl files can use it. The only restriction would be inheritance from external types. That can be solved by allowing types to be tagged as "manual". This would Prevent teal from adding its own OOP code on this type and instead it is up to the programmer to get the OOP working. A manual type can only be extended by another manual type. Perhaps it can also lift any restrictions that the teal version of inheritance opposes in the inheritance tree (So, if normally a type can only extend 1 other type, a manual type can implement as many as it wants). This way when writing pure Teal code you don't have to know how teal does it. From there it is possible to create extra tags if desired that change the generated OOP code to other variants that are popular (and can only be extended by types with the same tag, and/or the manual tag) and/or add meta programming tricks to help with the plumbing of working with manual types. |
Thinking about this a bit more: Maybe Teal doesn't have to care about how an individual OOP system is implemented, delving into every detail of metatables and overloaded For example, irrespective of how inheritance can be implemented, the expectation is that somewhere, the OOP library will store some information about what interfaces/classes were inherited from in a class that's returned by Of course, this wouldn't catch everything that you could program into a metatable. You could do something evil like overload So what would this kind of proposal look like in the real world? The problem with the definition syntax is that Teal requires that you declare a record's methods and fields up front, and you can't add any more after you've defined it. That means you have to rewrite a lot of Lua code that assigns methods to the class like local Color_ = class.class("Color")
local record Color<Color_>
r: integer
g: integer
b: integer
end
function Color:__construct(r: integer, g: integer, b: integer)
self.r = r
self.g = g
self.b = b
end
function Color:__tostring(): string
return ("%d %d %d"):format(self.r, self.g, self.b)
end
return Color The extra parameter to There are still things like autogenerated methods created on the -- build.tl
local class = require("build/shims/class")
return {
class_shims = { class }
} -- build/shims/class.tl
local function shim_for_class_class(t: tl.Type): boolean
if this_looks_like_a_class(t) then
-- In this example, `class.class()` adds a static :new() method that Teal doesn't see. We help out the compiler by indicating it exists here.
-- This is the same thing as saying `function new(...)` in a `record` declaration, but with the correct arguments programmatically generated based on the table returned.
-- It is the programmer's responsibility to ensure everything matches up with what the metatable will return.
local _construct = tl.get_method(t, "__construct")
tl.add_method(t, "new", construct.args)
-- Perhaps class.class() also adds a `__name` field, containing the class name for purposes like reflection.
-- This is the same thing as saying `__name: string` in a `record` declaration.
tl.add_field(t, "__name", tl.Types.String)
return true
end
return false
end
return shim_for_class_class The setup would be specific to each project and would not try to generalize, because there are infinitely many ways an OOP system could be implemented in Lua. But since you could reuse each shim between projects for the most popular OOP systems, it probably wouldn't be much of an issue. This next part is only a "nice to have", but for the sake of user experience, perhaps what Teal could provide on top of this system is some default OOP implementation using metatables that works for 90% of cases. All this would mean is adding some extra keywords for the common things people demand like local interface ISized
function size(table): integer
end
local class Rectangle implements ISized
x: integer
y: integer
w: integer
h: integer
function new(self: Rectangle, x: integer, y: integer, w: integer, h: integer)
self.x = x
self.y = y
self.w = w
self.h = h
end
function size(self: Rectangle) return self.w * self.h end
end One downside of this approach that I can see is it would prevent things like |
There are a lot of pre-existing OOP wrappers in the Lua ecosystem. They basically have functions that return tables that act as classes, and there can be much variance between them. Here is an example of one.
The idea is that
Color
is a special table with all the necessary bookkeeping for inheritance and mixins and similar things. It might also have autogenerated methods like:new()
, where a method like__construct()
is called internally by the class creation logic to mutate internal state and then returns it as a new "instance" of the class.The question is, if this were to be converted into
tl
, then what would the return type ofclass.class()
be? I tried the following:But I got these errors:
unknown type record
doesn't make sense, because the documentation states the following:So is
record
actually not a type in the sense ofstring
ornumber
since it can't be used as a return value?The text was updated successfully, but these errors were encountered: