Teal – A statically-typed dialect of Lua

229 generichuman 173 5/16/2025, 12:40:35 AM teal-language.org ↗

Comments (173)

pansa2 · 33d ago
> Teal is a statically-typed dialect of Lua.

I was expecting Teal to be "Lua + type annotations", similar to Mypy. However from a quick look it does indeed seem to be a "dialect" in its own right. Teal is Lua-like and compiles to Lua, but there's more to it than just static types. Perhaps it's more similar to TypeScript?

For example, Teal replaces Lua's tables - the language's signature single, highly-flexible data structure - with separate arrays, tuples, maps, records and interfaces. It changes the variable scoping rules and even adds macro expressions.

Teal therefore seems substantially more complex than Lua. The author recognizes this in the conclusion to a recent presentation [0]: Lua is "small and simple", maybe Teal is "something else"? Lua is for "scripting", maybe Teal is better suited to "applications/libraries"?

[0] https://youtu.be/Uq_8bckDxaU?t=1618

creatonez · 33d ago
It's interesting that you mention Typescript. In Typescript's early history, they added a bunch of features that they either thought would make it nicer for C# devs (classes, enums, option chaining, decorators, namespaces, etc.). Eventually, a bunch of these features were added to Javascript natively in nearly the exact same way they were implemented in Typescript. Now, the only remaining non-type-related features not added to Javascript are enums and namespaces, which will never be added because they're poorly designed. Even the Typescript type syntax (but with ignored semantics) may get added to Javascript under a WIP proposal. Some of these features were perhaps mistakes -- private in TS and private in JS will never be able to mean the same thing, and `class` syntax is iffy -- but overall there was an improvement.

By ambitiously adding useful features, could Teal push the upstream to make progress? Probably not because Lua's scope is intended to be small (and we're no longer in the same context as 2015 era Typescript and tc39), but it's interesting to think about.

koito17 · 33d ago
Minor nitpick: decorators are still in "stage 3". Not formally part of ECMAScript standard yet.[1]

Anyway, that has not stopped large parts of the JavaScript ecosystem -- notably Angular -- from using an experimental variant of decorators, such as the one provided by TypeScript. [2]

[1] https://github.com/tc39/proposal-decorators

[2] https://github.com/angular/angular/issues/48096

chamomeal · 32d ago
I feel conflicted about decorators, which I’ve only used in the context of nestjs.

They’re undeniably productive, but they’re very black box-ish. Just slap a decorator on a method, now it’s a cron job! Slap a decorator on, and now you’re logging the function call!

I feel like a lot of problems that decorators solve could also be solved with good ol’ higher order functions. Decorators also give zero (or limited? Idk) information to the typescript compiler, so you end up asserting a lot of types instead of inferring them.

I have all these gripes, but it really is amazing to throw decorators on stuff and have it work. Especially with third party libraries that provide decorators. I gave a nestjs app a queueing system by installing bullmq, then just slapping the bullmq decorators around!

Makes me think of the Rich Hickey “simple made easy” talk. Decorators are definitely not simple, which makes me naturally dislike them. But damn are they easy!!

WorldMaker · 32d ago
> I feel like a lot of problems that decorators solve could also be solved with good ol’ higher order functions.

The Stage 3 version of decorators are mostly just a syntax sugar for higher-order function composition. (As opposed to the rejected at Stage 1 version that did a lot more "reflection" and type meta-magic.) I personally was rooting for the Pipeline composition operator to win out first over decorators as what I feel a more generally useful higher-order function composition tool, but I understand given Java/C#/Python how much more people seem to love the decorator syntax.

WorldMaker · 32d ago
Even worse a lot of Angular ecosystem still relies a lot on a previous Decorators proposal that was withdrawn at Stage 1. If you are still using the `experimentalDecorators` flag in your build you aren't using the Stage 3 version of Decorators (which don't have a build flag, just a target requirement).
VoidWhisperer · 33d ago
Enums are possibly going to end up in JS eventually - this proposal[0] is at stage 1 (i know the readme says stage 0, it looks like there is a PR to update this). Granted, that means 'under consideration' but it is a start

[0]: https://github.com/tc39/proposal-enum

SoylentOrange · 33d ago
Just a small note about mypy and python - annotations are first-class citizens in Python3 and are not tied to any particular type checking system such as mypy, but are instead a core part of the language and actually serve vital functions in frameworks and libraries that are used to check interfaces such as Pydantic and FastAPI (eg URL params).

Mypy is just one type checker for Python, but there are many others including pyright. In fact pyright is quickly becoming the dominant checker over mypy.

pansa2 · 33d ago
Am I right in thinking that Python's type annotation syntax originally came from Mypy though?

IIRC Mypy started off as a type annotation syntax and corresponding type checker for Python. Mypy's type annotations were adopted by Python itself (in version 3.5 - PEP 484), which reduced Mypy's role to be just a type checker.

Since then, type annotations have indeed become a core part of Python - not only are they used in frameworks and libraries, but are also required to use language features like @dataclass.

thristian · 33d ago
No, Python's current type annotation syntax was added in Python 3.0 as a generic annotation syntax, in the hope that somebody else might come along and build a type-checker or other tooling on top:

https://peps.python.org/pep-3107/

MyPy was one such tool, and I think it had conventions for adding type annotations in comments, in places where Python didn't yet support them (such as variable assignment), but I'm pretty sure it was never a TypeScript-style pre-processor - type-annotated programs always ran directly in the unmodified CPython interpreter.

lifthrasiir · 33d ago
That was a major concern when I was using Lua at work. Pretty much every type checker in Lua required transpiling, which doesn't work for many environments (e.g. Redis script). My Kailua [1] was designed that in mind but didn't reach its full potential.

[1] https://github.com/devcat-studio/kailua/

90s_dev · 33d ago
> Teal replaces Lua's tables - the language's signature single, highly-flexible data structure - with separate arrays, tuples, maps, records and interfaces

They're all just Lua tables with specialized type checking for specific behavior.

I really wish the Lua authors would add official types to Lua. The time has come.

pansa2 · 33d ago
> I really wish the Lua authors would add official types to Lua.

Never going to happen IMO. Adding static types would change the nature of the language completely, even more than it has in Python.

As Teal shows, it would require giving up one of Lua's core features: tables as the language's single data structure. It would significantly complicate a language known for its simplicity.

Even the implementation would need to change radically - adding a type checker would invalidate the current approach of using a single-pass source-to-bytecode compiler.

dottrap · 33d ago
>> I really wish the Lua authors would add official types to Lua.

> Never going to happen IMO. Adding static types would change the nature of the language completely, even more than it has in Python.

You both are kind of right.

The Lua authors have been working on the new companion language to Lua named Pallene. Pallene is a subset of Lua that adds types, not for the sake of types themselves, but for the purpose of performance. The Pallene compiler can generate optimized native code that potentially removes the need to manually write a module for Lua in C.

The other cool trick is that Pallene and Lua are completely interoperable with each other, so Pallene can be added to existing Lua projects, and you can opt to use regular Lua for the dynamic parts of your code where compilers won't be able to optimize much and strong types might be more trouble than help.

Here is a talk Roberto Ierusalimschy gave about Pallene. https://www.youtube.com/watch?v=pGF2UFG7n6Y

90s_dev · 33d ago
Docs https://github.com/pallene-lang/pallene/blob/master/doc/manu...

Looks similar to Teal.

This is relatively exciting.

Also, called it!

Disappointed that it maintains syntactic Lua compatibility. Would have been a good time for a clean slate on the shoulders of hindsight.

andre-la · 32d ago
> Looks similar to Teal.

That's because they share a common origin from Typed Lua and Titan languages:

https://teal-language.org/book/other_projects.html

90s_dev · 32d ago
Wait, Pallene just compiles to C using whatever local C compiler?

https://github.com/pallene-lang/pallene/blob/master/src/pall...

Well that's kinda disappointing. I expected something more in 2025, like directly generating asm like a lot of languages are starting to do.

And your article makes it ambiguous whether it's from the Lua authors or grad students. I assume it started out just the students and then the Lua authors joined in?

dottrap · 32d ago
One of Lua's goals has been extreme portability, and the main implementation works on anything that has a C compiler, going to the extreme of compiling cleanly on C89, C99, and even compiling as C++ (no extern "C"). Remember that Lua is popular in the embedded space too, so this is a big feature.

Pallene isn't designed to be a new native language on its own. Pallene is designed to be a companion language for Lua, specializing in a subset of performance.

But as importantly, Pallene isn't just compiling to C. Pallene is generating C code that directly manipulates the underlying Lua internals, which are in C.

The research thesis is that many bottlenecks are due to boxing and unboxing going through an FFI. Memory safety also incurs overhead. Python is an extreme example of how excruciatingly slow this can be, but even Lua incurs costs for this. A core tenant of the Pallene compiler is that it can generate C code that gets to cheat like crazy. Pallene gets to directly access Lua internals and things like arrays directly manipulate underlying C arrays deep inside, which sidesteps boxing/unboxing. The compiler can do the analysis to make sure it doesn't cheat in a way that is unsafe. Finally, the C optimizer now also has a chance to perform optimizations. And now operations such as crunching math on arrays of numbers may get much faster because now you get generated code that is more CPU friendly and may benefit more from prefetching and cache locality.

Pallene is built from the the extreme compatibility goals as Lua since it is designed to work with it. It it only depends on any C compiler and Lua itself. If you get Lua compiled, then you can get Pallene working. That means any existing project that uses Lua (5.4) could start adding Pallene modules to their project for new features or to try to improve performance in key areas. Since Pallene just outputs a Lua modules, it looks like any other Lua module implemented in C, so it won't create new portability constraints that you didn't have before. This is different than say LuaJIT, where not all platforms may allow JIT or you may be targeting a new CPU architecture that LuaJIT does not support.

Both Teal and Pallene were started by grad students of Roberto's. Since Roberto has started giving talks himself on Pallene, I'm assuming they are joining in.

ufo · 32d ago
I'm Pallene's lead maintainer. Currently the code is maintained by me, my students and other open sou rce collaborators. We collaborate with Roberto over some Pallene-related research, specially about the type system semantics, but he isn't an active committer.
90s_dev · 32d ago
That's really exciting work. Great job. Hope it's fulfilling.
90s_dev · 32d ago
That's actually really exciting in that case.

If it goes further and generates native C control flow statements when possible ("if", "for", native functions, native function calls, etc), I think it could be an omni-level language, generating basically Lua statements when dynamic and C when not, and mixing them all within the same program, entirely controlled by how much type information you give it (and how you use tables and metatables).

ufo · 32d ago
Alas, as of last month we changed Pallene's compiler and it now generates C gotos for control flow. ( ^ _ ^ メ ) It helped with certain optimizations...
90s_dev · 32d ago
Ha! Called it again!

But why gotos instead of proper control flow? Is it just easier to emit?

ufo · 32d ago
We switched from a hierarchical internal representation to a flat one, with gotos. Made it easier to write textbook otimization algorithms. In theory we could try to reconstruct the if and while statements when it's time to emit C, but gotos are more straightforward now.
90s_dev · 32d ago
This sounds like it could compete with V8 at some point in the next few years. Honestly it could be a game changer. Looking forward to watching its progress.
lifthrasiir · 33d ago
In reality, tables are used in specific fashions and not fully generally. (Partly because Lua itself even recognizes some of these fashions and provides relevant operations accordingly.) Lua tables are not really a single data structure; it is a single type that acts as multiple data structures at once.
90s_dev · 33d ago
Yeah Lua optimizes array tables for performance as long as they're arrays. That's the only optimization I'm aware of. I get why they didn't just add arrays, to keep the syntax and semantics clean, simple, and unambiguous. I just don't like it. If you take that to its extreme, you get Lisp. Natural human languages are messy and full of warts, but they work despite that, or perhaps because of that, because human life is messy and warty. "Pure" languages never can or do catch on. My favorite language right now is unironically TypeScript despite all its baggage.
ufo · 32d ago
More than just keeping it simple, it's also about reducing the API surface of the language. Lua's main design constraint is that it's meant to be embedded inside other applications, and the versatile table type helps a lot in that regard.
tiffanyh · 33d ago
> As Teal shows, [official typed Lua] would require giving up one of Lua's core features: tables as the language's single data structure.

Is that true ... you can't have typed tables without giving up tables as a data structure?

krapp · 33d ago
You can't have typed tables without giving up tables as the language's single data structure. You would have tables and typed tables which are essentially just arrays with extra steps.
90s_dev · 33d ago
Not with type erasure like TypeScript does. Then it would just be type checking hints as to how the table is used, not a different kind of table. Teal does this.
wyldfire · 33d ago
> Perhaps it's more similar to TypeScript?

Funny you should mention that:

> It aims to fill a niche similar to that of TypeScript in the JavaScript world, but adhering to Lua's spirit of minimalism, portability and embeddability.

Benjamin_Dobell · 33d ago
You can get pretty far by bolting annotations onto Lua (no compilation step), for example using my IDE:

https://github.com/Benjamin-Dobell/IntelliJ-Luanalysis

Admittedly, I've been focused on some other things recently, but still with some focus on type safety e.g. https://breaka.club/blog/godots-most-powerful-scripting-lang...

ufo · 33d ago
I'm not sure if I follow.

Teal still compiles all those things into plain Lua tables, it's just that the type system has different table subtypes for better type checking. I think the variable scoping is also the same as regular Lua?

TulliusCicero · 32d ago
> Perhaps it's more similar to TypeScript?

I mean that's exactly what the page says, doesn't it?

> It aims to fill a niche similar to that of TypeScript in the JavaScript world, but adhering to Lua's spirit of minimalism, portability and embeddability.

0xFEE1DEAD · 33d ago
Years ago, I tried lua and wasn't impressed. Then I started using Neovim, did the necessary configuration in lua, but continued writing my own scripts in vimscript. Later I started using wezterm and decided to give lua a second shot, and I began to really like it.

I realized my initial dislike for lua stemmed from my experience with javascript (back in the jwquery days), where maintaining large codebases felt like navigating a minefield. The lack of type system made it all too easy to introduce bugs.

But lua isn't like that. It's not weakly typed like javascript - it's more akin to pythons dynamic duck typing system. Its simplicity makes it remarkably easy to write clean maintainable code. Type checking with type is straightforward compared to python, mostly because there are only five basic types (technically seven but I've never used userdata or thread). And I even started to enjoy using metatables once I understood how and when to apply them.

That being said, lua's lack of popularity probably stems from its limited stdlib, which often feels incomplete, and the absence of a robust package manager. luarocks is a pain to work with.

All that being said, I don't really see the point of using this project.

While I do wish type annotations were a native feature, the ones provided by the lsp are good enough for me.

augusto-moura · 32d ago
BTW, you might want to check Lux [1], it's a new approach for Lua packaging. They launched it recently, so there's a lot of work going on. But it looks and feels very promising

[1]: https://github.com/nvim-neorocks/lux

0xFEE1DEAD · 32d ago
Oh wow I didn't know someone was actually working on an alternative. It really does look promising. Thank you, definitely going to try it.
lr1970 · 32d ago
> That being said, lua's lack of popularity probably stems from its limited stdlib, which often feels incomplete, and the absence of a robust package manager. luarocks is a pain to work with.

And indexing arrays starting from 1 rather than 0.

pull_my_finger · 32d ago
It doesn't "index arrays from 1", it doesn't have arrays, but tables, that can operate as "sequences". It's all documented in the docs.

>>> A table with exactly one border is called a sequence. For instance, the table {10, 20, 30, 40, 50} is a sequence, as it has only one border (5). The table {10, 20, 30, nil, 50} has two borders (3 and 5), and therefore it is not a sequence. (The nil at index 4 is called a hole.) The table {nil, 20, 30, nil, nil, 60, nil} has three borders (0, 3, and 6) and three holes (at indices 1, 4, and 5), so it is not a sequence, too. The table {} is a sequence with border 0. Note that non-natural keys do not interfere with whether a table is a sequence

fanf2 · 32d ago
Strings are indexed from 1 as well, which can be pretty annoying. When I was splitting strings into even-sized pieces it was annoying that I kept having to work with odd numbers.
giraffe_lady · 32d ago
> Its simplicity makes it remarkably easy to write clean maintainable code.

Not based on my experience with even just medium-sized lua codebases. Anything over a few thousand lines and in continuous development has been a mess. Not lua's fault per se¹ but the only thing in my experience that compares is what you'd see in pre-laravel php. Every significant codebase is a messy ad hoc one-off framework in its own right.

A lot of people, as always when it comes up, are speaking of their recreational, small-project or config system lua code. Which is fine, it's good for that. But I have a lot of professional experience working in live production lua codebases and my experiences with it are different over there.

¹ A lot of large lua projects started as someone's first lua project or maybe even first code project at all, which is a tremendous accomplishment for a language but not a smooth ride for maintainers taking over those projects.

90s_dev · 32d ago
> But lua isn't like that. It's not weakly typed like javascript - it's more akin to pythons dynamic duck typing system

What? No, Lua's type system is practically identical to JavaScript's.

Even metatables are extraordinarily similar to prototype chains via __index (though much more powerful since they allow for operator overloading, which I wish JS had).

unscaled · 32d ago
I've had a similar journey to you — I've gotten familiar with Lua through using embedded Lua in apps like Neovim, Hammerspoon, Sbarlua and Wezterm. But unfortunately I feel the exact opposite: the more I use Lua, the more I hate it.

Lua doesn't have as many warts as languages like JavaScript or PHP do. The worst offenders are probably the 1-indexing (more of an stdlib issue than a language issue) and variables being global by default (same as JavaScript). It's a minimalist language and I guess this is one of the reason it is so popular as embedded language. But that's exactly why I find myself preferring even (modern) JavaScript to Lua. Lua is so barebones it's just too painful to use for anyone who got used to programming in other languages.

> That being said, lua's lack of popularity probably stems from its limited stdlib, which often feels incomplete, and the absence of a robust package manager. luarocks is a pain to work with.

And that's the crux of it. I guess many people are fine with writing tons of WET code using a barebones library and a bunch of copy-pasted code files thrown around when it comes to their personal scripts. That's all fine, but my brain isn't wired that way and I just feel excruciating pain every time I realize I have to write Lua again.

Luarocks is painful to use, but the main problem is that every embedded Lua you use deals with packages differently. Lua 5.1, 5.2, 5.3, 5.4 and LuaJIT are all incompatible with each other, since every version has breaking changes. Additionally, due to the lack of a standard library, many lua packages have to rely on native code, which makes portability and compatibility even worse. It all means that Lua doesn't really have a package ecosystem.

> But lua isn't like that. It's not weakly typed like JavaScript - it's more akin to pythons dynamic duck typing

I don't understand how Lua is less weakly typed than Java. Both of these languages have dynamic typing and do not support any type annotations or type inference in their core dialects, and both languages have "duck typing".

Where I feel JavaScript is vastly superior to Lua is tooling. JavaScript linters and language servers are far more advanced than Lua language servers, to the point where they can detect a lot of errors that I would waste hours on debugging with Lua. Due to Lua's atrociously bad error messages, without writing a lot of error handling code and copious printf statements, it would be quite hard to find where you've even made a typo in a variable name once your code gets large enough.

So yeah, I get why for some people Lua can be fun, because it's conceptually minimalist, but in practice I hate it with passion.

pmarreck · 33d ago
I've been diving into Lua (a little late to this party, but turns out it's a perfect language to rewrite some commandline scripts I had that were getting unwieldy in Bash, especially with LLM assistance!) and it's really something of an eye-opener.

LuaJITted Lua code runs at 80% (on average, sometimes faster!) of the compiled C version of the same algorithm, typically. Lua is embedded in a surprisingly massive number of products: https://en.wikipedia.org/wiki/List_of_applications_using_Lua The startup time of a script is in nanoseconds. An "echo" written in Lua runs faster than the native echo implementation.

The only warts so far are 1-based indexing (you get used to it), and the fact that LuaJIT is stuck at Lua 5.1 while Lua itself is up to 5.3 or 5.4 and has added some niceties... with Lua proper running slower. And no real standard library to speak of (although some would argue that's a feature; there are a few options and different flavors out there if that's what you need, though- Such as functional-flavored ones...)

Anyway, there's nothing else like it out there. Especially with its relative simplicity.

There are also some neat languages that compile to (transpile to?) Lua, and deserve more attention, such as YueScript https://yuescript.org/, which is a still actively-updated enhanced dialect of MoonScript https://moonscript.org/ (described as "Coffeescript for Lua", although it hasn't been updated in 10 years) although neither of these are typed. HOWEVER... there IS this: TypescriptToLua https://typescripttolua.github.io/, which takes advantage of ALL the existing TypeScript tooling, it just outputs Lua instead of JS!

Rochus · 33d ago
> LuaJITted Lua code runs at 80% (on average, sometimes faster!) of the compiled C version of the same algorithm, typically

Cannot confirm this. It might be true on selected micro benchmarks. Here are the results of the Are-we-fast-yet benchmark suite, which includes a decent set of benchmarks challenging CPU, cache and memory access: https://github.com/rochus-keller/Oberon/blob/master/testcase....

On average, the C and C++ implementations are five times faster than LuaJIT.

> There are also some neat languages that compile to (transpile to?) Lua

Here is a comprehensive list: https://github.com/hengestone/lua-languages. Lanuages like Oberon or Luon directly compile to LuaJIT bytecode (i.e. not to Lua).

Symmetry · 33d ago
The code in Are-we-fast-yet has been heavily optimized. It might still be true that naive LuaJIT can run almost as fast as Naive C.
Rochus · 33d ago
Have a look at the results and the code; there are benchmarks in the suite where the (ideomatic) C/C++ implementation is "only" twice as fast as the corresponding (idiomatic) Lua implementation, but on average (geomean of all factors) it's about five times as fast. The guidelines of the benchmark are pretty strict to enable fair comparisons (see https://github.com/smarr/are-we-fast-yet/blob/master/docs/gu...).
pmarreck · 32d ago
I looked at the docs. This seems to be comparing against Lua, which is why I specifically said LuaJIT

This is quite a distinction to be made. Can you clarify?

Directly from their guidelines page:

    Lua
    
    We write code compatible with Lua 5.1, 5.2 and 5.3.
    Smalltalk/Ruby symbols are represented as normal strings.
    We use Lua 1-based array and the length operator #.
    We use single object when a class is not required.
    Bitwise operators with various Lua versions is a nightmare.
    We use luacheck as a linter.
If they are writing code compatible with Lua 5.2 or 5.3, then that cannot be LuaJIT, which is ONLY compatible with Lua 5.1. (Unless they mean that they JUST write 5.1 code, which due to backwards compatibility is runnable on 5.2 and 5.3? It's unclear from here.)
Rochus · 32d ago
It's essentially written in Lua 5.1 with specific alternative implementations of mandelbrot and hashindextable for Lua 5.3 selectable by the test runner. But this doesn't matter much because my reference is LuaJIT. I have also compared different LuaJIT and also PUC Lua implementations, see http://software.rochus-keller.ch/are-we-fast-yet_LuaJIT_2017... and http://software.rochus-keller.ch/are-we-fast-yet_Lua_results....
pmarreck · 32d ago
This actually gave me an idea that I'm going to build out
jwatte · 32d ago
Another one of the biggest uses of Lua outside the hyperscaler-type software like nginx or redis: Roblox. Soooo many kids run games on Roblox every day!

Roblox not only runs entirely on Lua, but they've been working on their own type inference version of Lua named Luau, and open sourced it, and it's still in very active development.

https://github.com/luau-lang/luau

const_cast · 26d ago
> but turns out it's a perfect language to rewrite some commandline scripts I had that were getting unwieldy in Bash

IMO this is the perfect use case for Perl. I mean, it's why Perl was invented, but to this day it remains incredibly good at doing this specific task. It's incredibly easy to write and can be very easy to read, and it's much more robust than bash. But best of all - and the reason I think Perl still has a place in the modern world - it's available on practically every computer on Earth. And it will all work. It's so backwards-compatible that 25 year old scripts will run just fine. It's so portable that nothing even comes close. Also, it's shockingly fast. Faster than you would think. Regex is really powerful and it's been the template for so many regex implementations, but definitely be careful of runaway time complexity.

Anyway, Lua is great too. Really nice for embedding into applications ala VBA. Great for config.

pmarreck · 25d ago
Good point about Perl. Why don't I consider Perl? I don't know.
const_cast · 23d ago
Don’t worry, nobody considers Perl
kanbankaren · 33d ago
It is true that LuaJIT is stuck at 5.1, but you could write any performance critical sections in C/C++ and call it from Lua.

Lack of LuaJIT for 5.1+ isn't that big of a deal for desktop apps. The embedded world is still stuck in 5.1, but for them, the benefits of the latest Lua is marginal.

Internal server error