I’m not really sold on why Go would be a particularly good fit for the task, compared to other languages. Seems to be mostly “interfaces are useful for interpreters, and GC means I don’t have to care about memory management” - both of which are true but hardly specific to Go. There’s some form of interface in almost every mainstream language, I think the implementation would be pretty much the same in any GCd language
I never thought that Go would be a technology of choise for PL stuff. I always considered it more of a Java-lite for Web systems and also for CLI stuff, but here we are! TypeScript rewrote their compiler to Go. Being a compiler, they had no use of piggy backing on the GC, looks like they just liked the language.
Typescript chose Go specicfically because they didn't rewrite it. Go has close enough semantics to TypeScript that they could write a Go backend for tsc and do a machine port to Go and continue working from that
I used Go awhile ago for a Blockly interpreter. It's remarkably friendly; if memory serves, the ability to attach metadata to structs was pretty useful.
One hack I'm not super proud of is I implemented return and break with panic / recover. Were I to do it all over, I'd probably use continuations to model that instead.
(Side-note on Lua specifically: among the reasons I like Lua is that it's very simple and its reference interpreter implementation is some very understandable code. I did some truly horrible things to it back in the day for a game engine, and it was very amenable to getting beat up like that).
I think that’s re-interpretation, making the subject of the article about “why go rocks” shows the author thinks Go has something over other languages. Otherwise it’s like saying “why orange cats are fluffy” then saying stuff that applies to all cats without orangeness specificity
I know the author starts the post by saying he won't explain the reasons why he had to write a new Lua interpreter from scratch, but I'm still curious about that. Does anyone know? I dug through some of the links in the post and couldn't find the answer.
Yup, as others have mentioned, it's for zb. I originally outlined some background on design goals for this blog post, but the outline for that section was as big as the outline for the rest of the post, so I cut it. I'll probably write that one up as another blog post in the near future. :)
The exact reasons aren’t important for this blog post, but neither the reference implementation [...] nor the other open source Go Lua intepreters I could find were a good fit for my needs.
I'm intrigued because presumably the shortcomings of the other implementations are important to how they chose to implement Lua here.
Given this is going to be used as a scripting language for a build system, it's possible they are trying to implement a restricted subset of Lua rather than than the full language.
Performance is scarcely mentioned. It probably doesn’t matter that much for a build system. But for projects where it does matter, you won’t get performance in the same ballpark as a Lua interpreter written in C if you use Go.
In particular, because Go is memory-safe, you don’t have the control over memory layout you need to do tricks like NaN-boxing. Anything that requires a union with a pointer (which will be every value in a dynamically typed language) must be represented as a value of interface type, and interface values are quite fat - 16 bytes on a 64-bit machine.
Interpreters are atypical programs to optimize. Few other programs use unions so much in their inner loop.
You can do raw memory access via the ‘unsafe’ package, so I don’t think it would be impossible to do NaN boxing in Go. It would be somewhat less convenient than in C.
Unlike C, Go is garbage collected. Hiding pointers from the garbage collector so it can’t trace them without breaking something seems tricky. There would need to be pointers to that memory somewhere else to prevent it from being collected.
And then since you’ve disabled the system GC, you need to write your own GC that understands NaN boxing to find out when memory can really be freed.
That feels not quite correct. Lua coroutines aren't separate threads and trying to farm them out to goroutines (which are separate threads) risks violating expectations you can assume with non-concurrent execution. With coroutines, you can fairly believe that your state isn't going to change when you're running until you yield.
Lua has a long history with gaming since the late 1990s. The LucasArts adventure game "Grim Fandango" (1998) used Lua internally, and more recently it seems to have become the standard language for "fantasy console" game development such as in PICO-8 and TIC-80.
Go routines are very nice. Way better than async await nonsense in other languages. if err != nil {return .., err} gets pretty tedious however. Everything tends to be very mutable / nullable as well which could be better. Also not really any faster than Java/C# - just another GCed language.
I’m not really sold on why Go would be a particularly good fit for the task, compared to other languages. Seems to be mostly “interfaces are useful for interpreters, and GC means I don’t have to care about memory management” - both of which are true but hardly specific to Go. There’s some form of interface in almost every mainstream language, I think the implementation would be pretty much the same in any GCd language
One hack I'm not super proud of is I implemented return and break with panic / recover. Were I to do it all over, I'd probably use continuations to model that instead.
(Side-note on Lua specifically: among the reasons I like Lua is that it's very simple and its reference interpreter implementation is some very understandable code. I did some truly horrible things to it back in the day for a game engine, and it was very amenable to getting beat up like that).
Can't every language rock for building a Lua interpreter? It isn't a competition.
Deleted Comment
(Also, my pronouns are she/her.)
and the beta announcement was also recently submitted, but without commentary https://news.ycombinator.com/item?id=44290692
In particular, because Go is memory-safe, you don’t have the control over memory layout you need to do tricks like NaN-boxing. Anything that requires a union with a pointer (which will be every value in a dynamically typed language) must be represented as a value of interface type, and interface values are quite fat - 16 bytes on a 64-bit machine.
Interpreters are atypical programs to optimize. Few other programs use unions so much in their inner loop.
Most stuff people use in C for writing interpreters with optimal performance, isn't ISO C, rather C compiler specific extensions, or inline Assembly.
All things being equal, same rules apply.
And then since you’ve disabled the system GC, you need to write your own GC that understands NaN boxing to find out when memory can really be freed.
I see it as so awkward that it’s not worth doing.
And the error handling is way better than catching stuff because it’s so explicit