Interfaces

Interfaces are, in essence, abstract records.

A concrete record is a type declared with record, which can be used both as a Lua table and as a type. In object-oriented terms, the record itself works as class whose fields work as class attributes, while other tables declared with the record type are objects whose fields are object atributes. For example:

local record MyConcreteRecord
   a: string
   x: integer
   y: integer
end

MyConcreteRecord.a = "this works"

local obj: MyConcreteRecord = { x = 10, y = 20 } -- this works too

An interface is abstract. It can declare fields, including those of function type, but they cannot hold concrete values on their own. Instances of an interface can hold values.

local interface MyAbstractInterface
   a: string
   x: integer
   y: integer
   my_func: function(self, integer)
   another_func: function(self, integer, self)
end

MyAbstractInterface.a = "this doesn't work" -- error!

local obj: MyAbstractInterface = { x = 10, y = 20 } -- this works

-- error! this doesn't work
function MyAbstractInterface:my_func(n: integer)
end

-- however, this works
obj.my_func = function(self: MyAbstractInterface, n: integer)
end

What is most useful about interfaces is that records can inherit interfaces, using is:

local record MyRecord is MyAbstractInterface
   b: string
end

local r: MyRecord = {}
r.b = "this works"
r.a = "this works too because 'a' comes from MyAbstractInterface"

Note that the definition of my_func used self as a type name. self is a valid type that can be used when declaring arguments in functions declared in interfaces and records. When a record is declared to be a subtype of an interface using is, any function arguments using self in the parent interface type will then resolve to the child record's type. The type signature of another_func makes it even more evident:

-- the following function complies to the type declared for `another_func`
-- in MyAbstractInterface, because MyRecord is the `self` type in this context
function MyRecord:another_func(n: integer, another: MyRecord)
   print(n + self.x, another.b)
end

Records and interfaces can inherit from multiple interfaces, as long as their component parts are compatible (that is, as long as the parent interfaces don't declare fields with the same name but different types). Here is an example showing how incompatible fields need to be stated explicitly, but compatible fields can be inherited:

local interface Shape
   x: number
   y: number
end

local interface Colorful
   r: integer
   g: integer
   b: integer
end

local interface SecondPoint
   x2: number
   y2: number
   get_distance: function(self): number
end

local record Line is Shape, SecondPoint
end

local record Square is Shape, SecondPoint, Colorful
   get_area: function(self): number
end

--[[
-- this produces a record with these fields,
-- but Square also satisfies `Square is Shape`,
-- `Square is SecondPoint`, `Square is Colorful`
local record Square
   x: number
   y: number
   x2: number
   y2: number
   get_distance: function(self): number
   r: integer
   g: integer
   b: integer
   get_area: function(self): number
end
]]

Keep in mind that this refers strictly to subtyping of interfaces, not inheritance of implementations. For that reason, records cannot inherit from other records; that is, you cannot use is to do local record MyRecord is AnotherRecord. You can define function fields in your interfaces and those definitions will be inherited (as in the get_distance and get_area examples above), but you need to ensure that the actual implementations of these functions are resolved at runtime the same way as they would do in Lua, most likely using metatables to perform implementation inheritance. Teal does not implement a class/object model of its own, as it aims to be compatible with the multiple class/object models that exist in the Lua ecosystem.