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

Type narrowing proof of concept #577

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

fgaz
Copy link

@fgaz fgaz commented Nov 3, 2022

This is a proof of concept of type narrowing à la mypy. It successfully compiles the following teal code:

local record Is<T> end -- Won't be needed in a complete implementation

local record MyRecord
   a: number
end

local record OtherRecord
   a: boolean
end

local r : MyRecord | OtherRecord = { a = 1 }

local n : number

local function is_myrecord(x: any): Is<MyRecord>
   if x is table then
      local a = x.a
      return (a is number) as Is<MyRecord> -- casts won't be needed in a complete implementation
   else return false as Is<MyRecord> end
end
if is_myrecord(r) then
   n = r.a
end

And thanks to generics, even more complex stuff like

local function filter<A, B>(as: {A}, is_b: function(A) : Is<B>) : {B}
   local bs = {}
   for _, a in ipairs(as) do
      if is_b(a) then
         table.insert(bs, a)
      end
   end
   return bs
end

This is just to show how it can work. This minimal implementation has a number of problems:

  • Is is hardcoded and a dummy definition has to be provided
  • Is is a normal record instead of its own type (maybe this is fine though)
  • Booleans are not automatically promoted to Is
  • It breaks other tests (not sure if it's actual breakage or if the tests should be updated)

Related: #108 #124 #563

@github-actions
Copy link

github-actions bot commented Nov 3, 2022

Teal Playground URL: https://577--teal-playground-preview.netlify.app

@lenscas
Copy link
Contributor

lenscas commented Nov 3, 2022

With Teal being nominal typed rather than structurally typed, I don't think the code for is_myrecord is technically correct. However, I do get the idea behind this and I really like it.

@fgaz
Copy link
Author

fgaz commented Nov 3, 2022

Yes that was just an example, it could check that x.type == "MyRecord", call an external function... when you're interfacing with untyped stuff you have to rely on structure somehow.

By the way, looks like that filter function (incorrectly) typechecks even in regular teal: #578

@fgaz
Copy link
Author

fgaz commented Nov 15, 2022

  • added automatic boolIs<T> coercion
  • is_ methods now narrow self instead of the "first" argument

Is is still hardcoded, I haven't figured out how to define it within the compiler.

@fgaz
Copy link
Author

fgaz commented Dec 12, 2022

@hishamhm any feedback on this?

@fgaz
Copy link
Author

fgaz commented Mar 8, 2023

@hishamhm ping

is this something that could be included in the project in some form? or will teal follow another direction? typescript-style narrowing? dependent types?

@hishamhm
Copy link
Member

hishamhm commented Mar 9, 2023

@fgaz Sorry for not providing proper feedback earlier (for some reason I thought I did?? but I clearly didn't...)

I plan to add a way to specialize the behavior of is via interfaces (I made some notes here)... I expect it to handle the majority of use-cases that this kind of type-narrowing functions would address.

I'm not a fan of such utility types like Is<>; for such bespoke behavior I'd rather have specific syntax, rather than something that looks like a regular type.

In any case, this PR is good food for thought! I'll take the functionality you described into account when designing interface support (even if we end up supporting only a subset at first). As such, I think it achieved its goal as a proof-of-concept!

@fgaz
Copy link
Author

fgaz commented Mar 10, 2023

@hishamhm thanks for the feedback and link, much appreciated!

I can see why a utility type is not perfect. For example it can be misused by passing it around carelessly, as it's untied to the value it refers to.

But I think specific syntax has even worse drawbacks. Mainly, it isn't first class. In typescript, which has specific is syntax that is not a type (constructor), writing a filter function like the one in OP is impossible, it has to be provided by the compiler. How would you write filter with interfaces? How would it translate to lua?

I'm actually writing an article about all this and how some limited form of dependent typing would solve the problem neatly, I'll link it when/if I manage to publish it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants