Hacker News new | past | comments | ask | show | jobs | submit login
Luau goes open-source (luau-lang.org)
351 points by ingve on Nov 3, 2021 | hide | past | favorite | 87 comments



From https://luau-lang.org/typecheck

> Luau’s type system is structural by default

I think that makes it the second (production-ready) language to have a primarily-structural type system, after TypeScript/Flow(/and Closure to some extent) for JS. I wonder if it will follow in their impressive footsteps or if it will tread new territory.

One of the things that Flow gets right that TypeScript doesn't yet support is switching to nominal type-checking between class instances. In TypeScript you can assign an instance of one class to an instance of another as long as they have compatible fields. Compare:

Flow: https://bit.ly/3jZx4rB

TS: https://tsplay.dev/mZaL1N

TypeScript doesn't have any understanding of prototypes or prototypal inheritance, which is a big gap considering that it's a fundamental aspect of JS.

I'm really hoping Luau will come to do the right thing with tables and prototypes. Perhaps it already does, but unfortunately there doesn't seem to be a Luau playground to try in the browser.


Luau already has some notion of nominal types to deal with the fact that the Roblox API is basically a ton of C++ classes that have been mapped over via the FFI. Some of these types are totally structurally identical and yet incompatible.

We'd like to afford the same kind of ideas for native Luau code, but we're not there yet.


Super happy to see this released, and also for all the kids getting started programming on Roblox learning that there is a better way than random tag-checked mush :-) Good on you all!


Pony, amusingly, has both structural and nominal typing. https://tutorial.ponylang.io/types/traits-and-interfaces.htm...


So has COBOL, sort-of.

A regular MOVE is sort-of structural. It moves field i of the source to field i of the destination. Source and destination need not be of the same type, though.

MOVE CORRESPONDING is nominal. It moves field Foo of the source to field Foo of the destination (leaving fields that do not have corresponding items alone)

In both cases, data may be converted, for example from alphabetic to alphanumeric.


> a big gap considering that it's a fundamental aspect of JS.

To be fair to Typescript, it's an insane fundamental aspect of JS.


Funny that you would mention that in a thread on a statically-typed language based on Lua. Don't you need some insane mechanism to be flexible at runtime anyways? Either it will be reflection, or something like prototypes (in OO languages).


What about Go and OCaml? And I'm sure there are others.


I think OCaml isn't just structural. For example, sum types (variants in OCaml) have a nominal form which is the default: https://ocaml.org/manual/coreexamples.html#s%3Atut-recvarian..., but also a structural form, polymorphic variants: https://ocaml.org/manual/polyvariant.html. I think it's the same for records, I didn't find a way to express the type "a record that has a certain field" as an argument of a function. You can do this with objects and modules I think, but not with records.


Structural records are in fact "object types" in ocaml, which you can get the shell to tell you by calling a random method name.

  # let f x : int = x#foo;;
  val f : < foo : int; .. > -> int = <fun>
Actually there is not really a type of "a record that has at least some fields":

  # type t = < foo : int; .. >;;
  Error: A type variable is unbound in this type declaration.
  In type < foo : int; .. > as 'a the variable 'a is unbound
Its hidden (row) polymorphism, it's not subtyping but type abstraction. We're implicitely quantifying over the actual type:

  # type 'a t = < foo : int; .. > as 'a;;
  type 'a t = 'a constraint 'a = < foo : int; .. >
For the record (ha!), for people who don't know ocaml here are "structural sum types", or polymorphic variants as we call them.

  # let x = `Bar;;
  val x : [> `Bar ] = `Bar
above: type of `Bar is a sum containing "at least" (polymorphic) `Bar

  # type int_opt = [ `Some of int | `None ];;
  type int_opt = [ `None | `Some of int ]
above: some "exact" nameless sum type, with arguments

  # type 'a t = [< `Foo | `Bar ] as 'a;;
  type 'a t = 'a constraint 'a = [< `Bar | `Foo ]
above: polymorphic type of sums which contain at most these two constructors.

refs:

https://dev.realworldocaml.org/objects.html

https://dev.realworldocaml.org/variants.html#polymorphic-var...


I forgot about OCaml (since I normally associate it with the more sophisticated typing stuff). I don't know if I'd completely count Go since interfaces only apply to methods, not fields


Disclaimer: Not a regular Go user.

I was thinking of how you can embed types in structs. You automatically dereference the fields and methods of the components directly from the outside.


Yep, in go the inheritance (using the term loosely) structure can apply to interfaces (which specify groups of functions) and structs (which specify groups of data) and roughly does the same thing in both instances.


> In TypeScript you can assign an instance of one class to an instance of another as long as they have compatible fields.

Fun fact: If you add a private field to a class it'll behave nominally in TS. This is because private fields kinda require nominal relations to function. So, in a way, it does support "switching" to nominal type checking for classes - the opt in is simply per-class.


It won't do variance [0] right, will it?

[0] https://flow.org/en/docs/lang/variance/


I think variance can be simulated by typing the private field as an existing covariant/contravariant/invariant type

  class CovariantFoo<T> {
    private phantom!: T
  }

  class ContravariantFoo<T> {
    private phantom!: (_: T) => void
  }

  class InvariantFoo<T> {
    private phantom!: (_: T) => T
  }
(inspired by Rust [0])

[0] https://doc.rust-lang.org/nomicon/phantom-data.html


I believe this is conceptually similar to how "brands" can operate to add HKTs to TypeScript/Flow

https://www.cl.cam.ac.uk/~jdy22/papers/lightweight-higher-ki...

https://cs.emis.de/LIPIcs/volltexte/2015/5231/pdf/21_.pdf


But variance depends on position where the value is used, not at the time of declaration, superclass/subclass at parameter/return value position can't be correctly encoded like this, can it?


I think most languages define variance at type definition level, notable exceptions being Kotlin which supports both [0][1] and Flow. But yeah, TS doesn't support (variable-)declaration-site variance which I didn't realize you were asking in my previous answer.

[0] https://kotlinlang.org/docs/generics.html#variance

[1] https://kotlinlang.org/docs/generics.html#declaration-site-v...


Doesn't OCaml support it as well?


I'm less familiar with OCaml and don't know off the top of my head. Doing a quick search I was only able to find references to type declaration variance [0], though I learned that Java also supports use-site variance too [1]

[0] https://blog.janestreet.com/a-and-a/

[1] https://blog.jooq.org/tag/use-site-variance/


Oh cool, hadn't realized this. Also thanks for your great work! I'm rooting for some of your exploratory PRs!


> I think that makes it the second (production-ready) language to have a primarily-structural type system

I assume we are talking about a static type system here? Many common "scripting" languages are structurally typed - what Python calls duck typing.


If any of the Luau devs are watching this, please flesh out the metamethods. I'd switch almost everything to Luau if they were improved.

They're the biggest PITA right now as designed in PUC-Rio, and I see that while you've improved upon __eq, other metamethods are still lacking.

In PUC-Rio, boolean equality operators FORCE a boolean result, regardless of what you return. Ideally they would allow returning any result type, which then can be coerced to boolean later (e.g. by an `if` statement), just like the arithmetic operators do.

Further, `__neq`, `__ge` and `__gt` do not exist. They should.

The lack of a proper metamethod design means that binding to e.g. Kiwi[0] is impossible without some incredibly fugly hacks. It has been a long-standing annoyance with Lua in an otherwise beautiful little scripting language (that I use frequently).

This looks quite nice - lots of attempts in this space but nothing that attempts to match Lua to this degree.

[0] https://github.com/nucleic/kiwi


Actually went ahead and wrote an RFC for this: https://github.com/Roblox/luau/pull/109


>> Further, while >= and > can both be reversed to <= and < (and thus adding them in this RFC might be considered excessive or unnecessary)

False. You need all operators because for database NULL-alike logic you cannot infer results from one half of operators.

    NULL < NULL == false
    NULL > NULL == false


I agree entirely, you should comment on the RFC for this usecase (binding to database objects). It would be super helpful. :)


Can’t you rewrite the second of those so it becomes the first, hence rewriting `>` into `<`?



You still don’t need `>` for any of those examples though, do you? For example, `NULL > 1` could be rewritten as `1 < NULL` (even though it can’t be rewritten as `not(NULL <= 1)`.


What do you mean by rewritten? In

    func_a() > func_b() 
I expect left-to-right execution order. Not that it should matter in a good code, but anyway.


Heck yeah.

I don't know what this fork changes, but I love how many game engines will take lua and expand it to fit their exact needs like this.

Very few scripting languages make it as easy to do as lua.

This is an interesting related project:

https://moonscript.org/

It compiles down to lua, and really beefs everything up. Interface is super nice too. You just put `require "moonscript"` at the top of the file and you're done, now just write your code in moon script.


To my understanding it’s not so much a fork of Lua as it is a complete rewrite in C++. They’ve created their own custom VM and everything. The closest version it has parity with is Lua 5.1.4, with a few compatible features from 5.2 and 5.3 backported.


The VM still has a lot of Lua 5.1 code. It's definitely a fork.


Wow, now that's even more impressive.

Makes me wonder if there's anything useful to "upstream" to vanilla lua here, or if the VM deviated too far.


PUC-Rio's Lua doesn't take contributions anyway, right? It's open source but it's not an open project. Similar to.. uh.. nethack, at least how it used to be :)

https://www.lua.org/community.html


There is also a Fennel[1] that builds a LISP on top of Lua.

[1] https://fennel-lang.org/


Everybody's focusing on the type stuff, which is nice -- but luau also just performance better than Lua in a bunch of cases. Arseny (from Roblox) has tweeted a bit about it. It's interesting to follow!


Who needs yet another progra.... oh, it's a gradually typed fork of Lua in production use at Roblox eyes slowly widen.


Can this transpile type-annotated Luau code to just regular Lua code? I would like to add type checks to my Lua code but I dont want to run it in the Luau VM. I want to run it in PUC-Rio Lua, and LuaJIT. Something like Haxe[1], but not an entirely different programming language.

[1] https://haxe.org/


There is Teal language that has type annotations and could output plain Lua: https://github.com/teal-language/tl


ooh neat


There is a transpiler in the source tree, but I expect it needs a polish pass. We haven't put it to use in quite some time.

In particular, I am pretty sure that it won't do the right thing when it encounters the extra syntax we've added to Luau. So far, that's an if-else expression and a continue keyword. Transpiling those accurately will take a bit of work.


Doubtful, but there is TypescriptToLua: https://typescripttolua.github.io/

Here's a whole list of languages that compile to Lua (many of them statically typed): https://github.com/hengestone/lua-languages


I highly doubt it. Most of the time with these forks they change/remove core language features instead of compiling down to plain lua.


It's worth noting that Luau is backwards compatible with Lua 5.1*, so I see no technical reason why transpilation wouldn't be possible.

* I'm tangentially aware of some RFCs to deprecate metatable related methods (setmetatable, getmetatable, rawset, etc), which may have an impact.


> using a combination of techniques pioneered by LuaJIT and custom optimizations that are able to improve performance by taking control over the entire stack (language, compiler, interpreter, virtual machine), we’re able to get close to LuaJIT interpreter performance while using C as an implementation language.

At first I was a bit surprised by this, given that they have much better type information, but they forget to mention until further on that Luau doesn't use JIT/AOT optimizations (yet). Still, it's as good an excuse as any to bring out this quote again:

“LuaJIT is much faster than all of the other languages, including Wren, because Mike Pall is a robot from the future”

- Robert Nystrom


LuaJIT is truly a marvel of engineering. I put it on par with the JVM itself in terms of things that I'm just completely blown away by.


Slight digression. IIRC PHP was using Mike’s work (DynASM) a while ago. Is this still the case and is anyone else using aspects of LuaJIT (eg DynASM or the byte code interpreter)?


To partly answer my own question - the Erlang team considered DynASM but didn’t use it in the end for their jit.

https://news.ycombinator.com/item?id=24459449


People seem to say this (or similar remarks) when talking about Lua but even more specifically, LuaJIT in particular but it seems like Lua is already a pretty niche language so am I just missing something? Like you can embed Lua in other languages but why use Lua instead of something like mruby for all the warm fuzzy feelings you get when writing Ruby?

Just curious, appreciate any context you can provide!


> why use Lua instead of something like mruby

The main reason is momentum - Lua has been the de-facto standard embeddable language for at least 15 years. For example, it's used in World of Warcraft which was released in 2004.

Another reason is the quality of the implementation - Lua has very few bugs [0], whereas Shopify tried to embed mruby and had to give up because it had too many security holes [1].

Finally, Lua is a complete language in itself, with a guidebook, reference manual and so on. Mruby (and MicroPython) are less well-known. Plenty of people know Ruby or Python, but if you use the smaller implementations, you never know when your code might hit a language feature that they don't support.

[0] https://www.lua.org/bugs.html [1] https://brandur.org/fragments/shopify-mruby


The advantage that Lua has is that it has about the simplest possible binding interface you can imagine for a Language.

There's no manifests, no class definitions, no anything really. It's as simple as calling lua_newstate to create a state, calling lua_pushcfunction to register some functions that expose your desired engine functionality, and calling lua_loadstring with a script to run.

That means you can use Lua in your engine for something as simple as a CLI interface that just exposes a couple dozen functions, and it's still worth it. Where with any other scripting language you'd have to be doing a lot more with it to make the setup cost worthwhile.

Having LuaJIT available if you end up wanting to do more with it later is the icing on the cake.


> it seems like Lua is already a pretty niche language

Keep in mind that what we see on the internet is a vocal publicly part. Some important domains are not publicly represented.

For example, in my domain CGI/VFX, Python is used all around, but lua has growth in some places. For example it is used in Guerilla[0] and Katana[1], two software related to rendering and scene management. Technically a niche, but commercial product too. So, groups of people uses it all day but you will not find questions about it on stack overflow.

The reason to choose lua despite having Python all around is it's lightweight, apps can hack the VM for your specific needs, function call cost is faster and you can easily expand it with C (compared to Python).

This is just an example related to my domain, but I suspect this pattern is similar in many domains that doesn't pop on the internet. I suspect web dev is overexposed on the internet, making people thinking it's how dev runs all around.

[0]: http://guerillarender.com/

[1]: https://www.foundry.com/products/katana


> Like you can embed Lua in other languages but why use Lua instead of something like mruby for all the warm fuzzy feelings you get when writing Ruby?

I mean, we're mainly talking about games here. Choosing performance over developer comfort is the default.


There is a language [1] that type check by adding comments instead of fixing Lua (like Flow comment syntax [2]), I wonder if the Luau team has considered this language.

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

[2] https://flow.org/blog/2015/02/20/Flow-Comments/



The compatibility page (https://luau-lang.org/compatibility) mentions a "pending GC rework". Does anyone know anything more about this?

I can recommend reading the performance page, it's a an engaging summary of the various tricks/optimisations that have been implemented https://luau-lang.org/performance


The first thing you do is to play - and one day you play on a computer.

And here it is you learn it all. How to operate a OS and what to expect of it.

How to install a program.

And how to program.

Which means in 15 years, lua like syntax will push aside java-script, because this generation learned it in another way. Might aswell embrace it ahead of time.

Learned Future Preference dominance is gaming market dominance.


I'm mostly wondering, what does this have over regular Lua, especially now with 5.4?


Lua's biggest thing is that it's pretty easy to embed inside a C/C++ application safely, for relative definitions of "safe" spanning "preventing the Lua code from segfaulting your application" to "preventing the Lua code from doing anything you don't explicitly give it the ability to do".

It's why you see it used as so many game's plugin architecture and why it was selected for NeoVim.

So there is a very large ecosystem of Lua code that just lives in Lua 5.1 and doesn't really push forward at all. I haven't checked in on the situation for some time, but most people who write Lua are writing Lua for something like NeoVim or WoW or some other plugin for a larger project. Those projects have no interest in dealing with the slow reference VM for Lua >=5.2 at this time.

Lua 5.2, 5.3, and 5.4 are more analogous to Python 2 to Python 3's level of change, rather than Python 3.9 to Python 3.10. These more recent versions of Lua 5.x are somewhat incompatible with each other, rather than just adding new language features.

Most products settle on Lua 5.1 because that is what luajit, a very fast just in time compiling implementation of the Lua VM supports. Plans to further support to new versions for luajit are...well they exist.


> WoW [...] have no interest in dealing with the slow reference VM

Does WoW use LuaJIT? I thought it used the reference Lua 5.1 VM, simply because that was the newest version when it was first released.

> Plans to further support to new versions for luajit are...well they exist.

I’m not sure it’ll ever happen. AFAICT LuaJIT is so complex that only its author, Mike Pall, understands it well enough to make major changes. He has expressed his dislike for several of the changes in Lua >= 5.2.


> Does WoW use LuaJIT? I thought it used the reference Lua 5.1 VM, simply because that was the newest version when it was first released.

I didn’t mean to imply WoW was using luajit, apologies. I was under the impression that over the years they have patched and fussed with code of the reference VM to resolve some performance pain points in the vanilla UI lua. Unfortunately, I can’t find the source for that.

Regardless, I’ve always assumed that they had little interest in both rewriting their vanilla UI to newer versions AND starting over from square one trying to resolve any performance hiccups they hit.


I can tell you from my own experience that luajit, which also targets 5.1 compatibility, is _significantly_ faster than Lua proper. In using it I've never missed 5.2+ features. So I imagine luau has the same thing going for it.


I would expect Luau to be significantly slower than LuaJIT though, because Luau doesn’t include a JIT compiler.


True, but they claim to have gotten as fast as LuaJIT in many cases and, important for their use case, JIT doesn't work on some platforms (iOS): https://luau-lang.org/performance

> On some workloads it can match the performance of LuaJIT interpreter which is written in highly specialized assembly.


Note how it says: "match the performance of the LuaJIT interpreter" (emphasis mine). LuaJIT basically has two parts: an interpreter in handcrafted assembly which is quite a bit faster than the standard Lua VM, and the actual JIT, which is even faster. IIRC on iOS the JIT is not allowed but the interpreter is still there.


Good catch! I missed the word interpreter in there.


Lua 5, unlike other foobars, is a complete language from the start. You may see 5.x as 5.a, 5.b, 5.c and so on rather than as “evolution” versions. The second digit is more like flavor, and as e.g. 5.2 or LuaJIT user you don’t miss 5.4 features. Moreover, some people, me included, find some of 5.2, 5.3 and 5.4 experiments questionable.

Why is it so? Usually, when a language unlike Lua “evolves”, it just lacks a flipping lot of core/meta features from the beginning, crutch-adding them later. So users long for 1.x+ version so much, because 1.x is still bullshit, for any x. In case of Lua, its 5.1 version had some flaws, but it already had (arguably) more powerful execution/data model-wise than e.g. python or js, and these flaws were related to what the latter two haven’t had at all (because to them Lua is Lisp, in a sense of blub paradox). Coroutines, environments, metamethods, etc. 5.2 solved all of the remaining problems one way, LuaJIT another way. Upgrading to >5.2 doesn’t really change much in a way it works, but it’s enough to break your existing codebase.

So, “especially now” doesn’t have much weight in this case.


Type annotations are the main benefit. It's also much faster than Lua 5.1, but I don't know how it compares to 5.4.


I really miss compound assignments and a continue statement. The if then else expressions look cool too.

Would love to see these in the LuaJIT but I think its considered "done"


Mike Pall has added two sponsored projects in 2021 http://luajit.org/sponsors.html I sure hope he's the world's best paid Lua contractor


implementation of continue from one luajit fork:

https://github.com/zewt/LuaJIT/commit/c0e38bacba15d0259c3b77...


> If you wear a grass skirt and lei, you will most likely fit in at a luau.

https://www.vocabulary.com/dictionary/luau, https://en.wikipedia.org/wiki/L%C5%AB%CA%BBau


Thank you for this. I kept trying to pronounce this as some combination of "lua" and a "u" sound, which made no kind of sense. I have apparently never seen the word "luau" out of context.


Does nobody else find this confusing naming? How do you pronounce that, and does it sound like lua?

Will subsequent derived languages be called luaua and luauau?


Loo-ow or /ˈlu.aʊ/. [1]

Assuming it's the same as "luau", which is a traditional Hawaiian feast, or –more frequently– a catch-all term for Hawaii-themed parties. [2]

1. https://en.wiktionary.org/wiki/luau#Pronunciation

2. https://en.wikipedia.org/wiki/L%C5%AB%CA%BBau


in Lua the emphasys is in the 'u', in Luau its in 'a'


Not according to their own front page: "Luau (lowercase u, /ˈlu.aʊ/)". It would also be very unlikely, because it would make it different from the usual pronunciation of the word "luau" [1], which it seems to be making a pun with.

[1] https://en.wiktionary.org/wiki/luau


This is helpful but somehow also makes it worse


I wonder if this fork will be backported to Lua, the extra features like strong typing maybe activated with a compiler switch or something.


Can't wait to see if Fennel can support this =)


The more I read about Lua, the more I like it. What's the recommended learning material for beginners?


Programming in Lua[1] is the K&R for Lua.

[1] https://www.lua.org/pil/


What is it with grafting type systems onto dynamic, interpreted languages? Can't we just leave them alone? There are plenty of statically-typed languages if types are required.


Because as codebases in dynamic languages grow and age the developers start seeing the advantages having a static type system to lean on would give them.


the only statically typed language that’s usable with roblox is roblox-ts pretty much, and that’s just an unofficial project that compiled ts to luau. there’s nothing wrong with adding types to luau.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: