Skip to content
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

Enhancement: Solutions to the union issues? #785

Closed
SlashScreen opened this issue Aug 19, 2024 · 3 comments
Closed

Enhancement: Solutions to the union issues? #785

SlashScreen opened this issue Aug 19, 2024 · 3 comments
Labels
feature request New feature or request semantics Unexpected or unsound behaviors

Comments

@SlashScreen
Copy link

In the current version of Teal, Unions cannot have more than 1 record type, and no string | enum. Here, I have some solutions:

For the Record problem- insert a function into the new records called _type() that returns a string with the record name, and use that instead of type() in the if-else chain generated when using is.

local a = {}
a._type = function()
    return "foo"
end

assert(a._type() == "foo")

For the enum problem: Instead of using a type() call, you can use Objects to emulate the "unique"ness of enums. Upon generation, insert a table into the generated code as a stand-in for the enum and an accompanying table set, and do checks against that. Instead of is using type(), it can instead simply check the type based on whether that value is within the enum's Set.

MyEnum = {
    north = {}, -- Unique object
    east = {},
    south = {},
    west = {},
}
MyEnumSet = {}
for _, v in pairs(MyEnum) do
    MyEnumSet[v] = true
end

assert(not("north" == MyEnum.north)) -- Equality check
assert(MyEnumSet["north"] == nil) -- Type check
assert(MyEnumSet[MyEnum.north] == true) -- Type check 2

The remaining question: What about normal tables that aren't records? Perhaps all the record-comparing can take place inside an if statement that checks if the value has a _type function. To union against several types of tables, perhaps you just need to have a loop that checks the shape of the table.

This I suppose introduces a bit of overhead; is this a worthy sacrifice?

@hishamhm hishamhm added feature request New feature or request semantics Unexpected or unsound behaviors labels Aug 19, 2024
@hishamhm
Copy link
Member

@SlashScreen Thanks for the feedback! The good news is that the most important of these use cases (support for various Lua object systems represented as Teal records) is already addressed in the code for the next version of Teal, available in the next branch.

unions and is for records and (new!) interfaces

In Teal next, you can declare a custom macro expression for a record via a where clause, which implements custom logic for the record's is operator. Multiple record types which implement is can be used at the same time in a union.

The _type solution you described, while neat, is not workable for Teal because it needs to support multiple object systems from different Lua libraries and frameworks, each of which having its own way to determine their object types. The macro expression system allows for a generic solution. For your own records, for example, you can implement _type() for them!

interface MyObject
   _type: function(self): string
   -- ...
end

record MyFoo is MyObject where self:_type() == "foo"
   -- ...
end

function MyFoo:_type()
   return "foo"
end

record MyBar is MyObject where self:_type() == "bar"
   -- ...
end

local function f(obj: MyObject)
   if obj is MyFoo then -- this does what you expect!
      -- ...
   end
   local v: MyFoo | MyBar -- this is legal!
   -- ...
end

(Note that Teal continues to not provide an object system of its own — constructing objects by setting metatables, etc, continues to be manual, the same way as in Lua and the current release of Teal.)

Documentation is still lacking, but for some concrete examples, see how the various Type records are defined in tl.tl for the next branch: https://github.com/teal-language/tl/blob/next/tl.tl

enums

no string | enum

Since enums in Teal are strings (and have to be since they were designed to map into concrete Lua libraries which use strings as effective enums), string | MyEnum doesn't make a lot of sense because those types are not disjoint, and in fact string contains all enums (i.e. string | MyEnum doesn't give you a different set of objects than string). But if what you really want is var is MyEnum, that's a different matter. We could support it with a set, but then there are other feature requests already about iterating over enums, etc. So it needs more design but it's not something I rule out. It might come in the future!

general tables

To union against several types of tables, perhaps you just need to have a loop that checks the shape of the table.

I'm careful to not add seemingly primitive operations which add computational overhead, or generate code which can trigger side-effects (think code expansion that outputs the same expression twice). The macroexp approach adopted for the next version of Teal will let that decision up to the user. If you want to allow your records to feature in unions, you can declare __is operations for them (typically via the where clause syntax sugar). I have no immediate plans to address things like unions of non-records. Some special cases could be handled by verifying the disjointedness of the table types at hand, but right now I feel like they're corner cases that don't strike the right balance between usefulness and added complexity to the compiler/language.

@SlashScreen
Copy link
Author

Oh, I see, so an enum isn't exactly a type; any string matching the enum is that enum.

Directions = {"north", "east", "south", "west"}
DirectionsSet = { -- This approach also allows for custom values in enums.
    north = 0, 
    east = 1,
    south = 2,
    west = 3,
}

-- iteration

for value in Directions do
    assert(not DirectionsSet[value] == nil)
end

-- is

assert(not DirectionsSet["north"] == nil)
assert(DirectionsSet["some other string"] == nil)

@hishamhm
Copy link
Member

Oh, I see, so an enum isn't exactly a type; any string matching the enum is that enum.

An enum type is a subtype of string.

DirectionsSet

Yes, that's what I meant by "we could support [is] with a set". :)

For the sake of issue tracker housekeeping, I'm closing this issue since it is a duplicate of #108 (see also #189, #205)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request New feature or request semantics Unexpected or unsound behaviors
Projects
None yet
Development

No branches or pull requests

2 participants