teal-language / tl

The compiler for Teal, a typed dialect of Lua
MIT License
2.12k stars 107 forks source link

Protocols #655

Open voltangle opened 1 year ago

voltangle commented 1 year ago

I have just discovered Teal and I instantly fell in love with it, because of how it made Lua miles better with type safety. But I can't find one feature that I really want to exist, and it is traits. In general, I see them basically the same as Rust traits, which are very powerful. Why add them? Well, instead of fiddling around with inheritance and subclassing and stuff you just add a trait to a record. Example:

local trait MyTrait
    foo(bar: number): string
end

local record MyRecord end

function MyRecord.new(): MyRecord
    -- creates a new MyRecord instance
end

implement MyTrait for MyRecord
    function foo(bar: number): string
        -- do something
    end
end

local function baz<T: MyTrait>(arg: T)
    arg.foo(0) -- perfectly legal
end

-- and multiple traits can be chained like this:

local function bazz<T: MyTrait + MyOtherTrait>(arg: T)
    arg.foo(0) -- perfectly legal
end

local recordInstance = MyRecord.new()
baz(recordInstance) -- perfectly legal
baz(1) -- nein, integer doesn't implement MyTrait

This example is basically a ripoff of how Rust did it Can this be a good thing to be added to Teal?

lenscas commented 1 year ago

one of the goals of teal is to stay close to lua. Considering that lua doesn't have traits I don't think that adding them to teal is good.

Teal needs interfaces and the like though, ideally with some way for types to have them be implemented structurally rather than only nominally, similarly how TS does things.

voltangle commented 1 year ago

Understood, then how the Teal team sees how a problem like this can be solved in Teal? I suggested a trait system so that logic can take in more generalised data instead of concrete types, which improves code portability and flexibility

voltangle commented 1 year ago

Changed the example:

global protocol MyProtocol
    foo: function(self: Self): string
end

local record MyRecord end

local function MyRecord.new(): MyRecord
    -- creates a new MyRecord instance
end

extension MyRecord: MyProtocol
  function foo(self: Self): string
    return "foo"
  end
end

-- first usage variant
local function baz<T: MyProtocol>(arg: T)
    arg:foo() -- perfectly legal
end

-- second usage variant
local function baz(arg: any MyProtocol)
    arg:foo() -- perfectly legal
end

-- and multiple protocols can be chained like this:
local function baz<T: MyProtocol + MyOtherProtocol>(arg: T)
    arg:foo() -- perfectly legal
end

local recordInstance = MyRecord.new()
baz(recordInstance) -- perfectly legal
baz(1) -- nein, integer doesn't implement MyProtocol