Hacker News new | ask | show | jobs
by kamac 2108 days ago
Why would they create their own obscure solution? I imagine LUA is very easy to run sandboxed, but I can’t shake the feeling roblox would be better off integrating scripting with JS. It would come with powerful typing via typescript, a great tooling (along with a type checker), and a lot of resources available online.
3 comments

That's a popular opinion among people who have only used Javascript.

Few who have used both would give it a second thought.

Roblox is immensely popular, has a wide install base and thousands of developers with code in Lua already.

Why throw all of that away for an inferior language?

It’s also especially ironic to vouch for Javascript, and then in the next sentence switch to Typescript, which was precisely created to overcome some of JS defects.
> That's a popular opinion among people who have only used Javascript. Few who have used both would give it a second thought.

I have extensively used both languages for years and you haven't adequately answered my previous criticism [1].

> Why throw all of that away for an inferior language?

As if Lua (either the language, the implementation or the ecosystem) is not inferior.

> Roblox is immensely popular, has a wide install base and thousands of developers with code in Lua already.

This is probably the only reason to justify Luau, and shows yet another problem with the Lua ecosystem: they don't (or rather, can't) share their creations to each other. JavaScript is not a perfect language but the JS ecosystem has successfully converged into a single widespread set of solutions including TypeScript.

[1] https://news.ycombinator.com/item?id=24067362

Alright, if I must:

I still have no idea what your stubborn bug is.

Asking someone else to debug your code is obnoxious; insisting on it, doubly so.

I've never encountered it in the wild, and it sounds like a 'you' problem. Please don't bring it up again.

Or if you simply must, explain yourself. This isn't a tech interview; you're not paying me to solve puzzles, or even promising to.

string.gsub receives at most three arguments where the final optional argument is the maximum number of replacements, and returns two values where the second value is the number of replacements made. Therefore if the arguments do not have escape sequences the outer string.gsub receives 0 and `<arguments>` doesn't get replaced.

I intentionally asked you to find this bug out because you can acknowledge the particular class of bugs only after biten by that bug, and you didn't seem to even know what might be problematic. In the other words, by now you can't get away by saying "you should give all function calls a name" posthumously.

It's a frequent novice mistake to write `if (a = b)` in C/C++, but any good enough C/C++ programmer will point it out (and modern compilers will flag a warning). Eventually people get used with this problem and the cycle repeats, this time with less novices falling into the trap, so this class of bugs is---while problematic---not considered a huge deal. Given your reaction though, I doubt this is the case for Lua and that's yet, yet another reason to avoid Lua.

Edit: ah, I just realized what you're actually complaining about.

Being able to drop two arguments into a function call is great! It's one of the better things about Lua.

But when you don't want it, wrap the call:

    f(a(b,c)) -- this will call f(r1,r2) with two returns
    -- vs
    f((a(b,c)) -- this will always call f(r1)
And now you know how to prevent this class of bug. Catching it in an intermediate variable is possible but by no means necessary.

Original reply follows:

Alright, yeah, that kinda sucks.

Here's another dumb one: table.insert, if you give it two values, it's table.insert(tab, val) and it inserts at #tab + 1.

If you give it three values, it's table.insert(tab, index, val), and that's bad enough: but if you pass it (tab, val, nil), it interprets val as index.

Which is surprising! you'd think f(a,b) and f(a,b,nil) would be the same thing, and usually, they are. But not in this one case.

But to be fair, here's an example of Lua done well:

    local L = require "lpeg"
    
    local end_str_P = L.P "]" * L.P "="^0 * L.P "]"
    
    local function _disp(first, last)
       return last - first - 2
    end
    
    -- capture an array containing the number of equals signs in each matching
    -- long-string close, e.g. "aa]]bbb]=]ccc]==]" returns {0, 1, 2}
    
    local find_str = L.Ct(((-end_str_P * 1)^0
                          * (L.Cp() * end_str_P * L.Cp()) / _disp)^1)
I'll let you reason about what it does, and why it would be such a pain in the butt with regular expressions.
> But when you don't want it, wrap the call: [...] And now you know how to prevent this class of bug. Catching it in an intermediate variable is possible but by no means necessary.

Well, I knew that; in fact what I've linked in the past discussion was the exact code to handle this case in my type checker. It is seriously confusing that a seemingly excess parenthesis affects the code anyway, and you can't exactly know whether the parenthesis is necessary or not without using a type checker.

There are some other languages that have multiple returns and can pass multiple arguments for nested calls, notably Go, but Go only does that when the inner call is the sole argument to the outer call. And Go does have a type system. Tons of other languages including JavaScript have an explicit "spread" operator which mostly retains the usability and avoids such problems.

> Which is surprising! you'd think f(a,b) and f(a,b,nil) would be the same thing, and usually, they are. But not in this one case.

Or any C function relying on lua_gettop. Seriously, nil should have eliminated in that stack.

> I'll let you reason about what it does, and why it would be such a pain in the butt with regular expressions.

I don't like a quirky EDSL alternative to the regular expression (or what it matters, parsing expression grammars) either. I prefer lpeg.re in that regard.

    local re = require "re"
    local long_str = re.compile(
        [[
            end_str <- ']' '='* ']'
            long_str <- {| ((!end_str .)+ ({} end_str {}) -> disp)+ |}
        ]],
        {
            disp = function (first, last)
                return last - first - 2
            end,
        })
Lpeg itself is a fine library, but its use of 1 as anything or -1 as the end of string is annoying (at the very least `lpeg.P(-1)` should have been `lpeg.Eos` instead). I would still use lpeg proper for constructing patterns programatically.

But you can't give lpeg as an answer to string.gsub and so on. First, the standard library still remains problematic and people will get tripped up (if you have no room for implementing `|`, one can require that `|` is escaped, m'kay?). Second, it's not a default, or at least not what you can easily `require`. The Lua ecosystem is fragmented primarily by not having a universally usable package system (I stress that LuaRocks is not) thus an external library is not always an option.

By the way I don't know why you would want to give that obscure example to brag about Lua. Your code is in fact slightly incorrect: `(L.Cp() ...) / _disp` looks like that the capture is applied only to that parenthesis but it's not, thanks to the operator precedence. The main problem of regular expressions is an inability to refactor and you haven't correctly demonstrated that.

I'd love to see your list of problems with the language, if you can share sometime.
My standard argument against Lua is available from [1]. I have said a lot about Lua, so you can use the search [2] for the partial listing.

[1] https://news.ycombinator.com/item?id=18351788

[2] https://hn.algolia.com/?dateRange=all&page=0&prefix=false&qu...

I have a pretty decent experience in lua, js, python, perl and a few in other scripting languages, and personally wouldn't just throw js away in general. Yes, it is a legacy freak, but some latest parts of it are at least worth adopting in other languages. To name a few, constructing objects ({a, ...b, c:[...d, e]}), destructuring, arrow functions conciseness, proxies, default arguments. This helps much in scripting and libraries for scripting. Also I find separate objects and arrays being a better approach than just lua-tables. I like lua, really, but some parts of it... you have to struggle with constantly. Select and "..." sematics in general, meta restrictions, a bloaty syntax.

Imo, the next language with a little stricter typing than both js and lua, which takes best parts of these and gets rid of bad practices, and with a typed version in mind, will be a big win for everyone.

I upvoted this, although I disagree about tables vs. separate objects and arrays. If you were to say separate maps and a specialized array type, I would be more inclined to your point of view! Not sold completely, mind you.

In either case, it's a productive disagreement. I agree that Lua isn't a perfected language; merely a very, very good one.

From your list, I would takeaway default arguments as the first thing to bring over to this hypothetical successor language. Arrow functions could be avoided by the simple expedient of renaming the keyword `function` to simply `fn`.

What are proxies for?
> Roblox is immensely popular, has a wide install base and thousands of developers with code in Lua already.

100% agree till here:

> Why throw all of that away for an inferior language?

I'd say "not the right tool for the job, given their target audience".

Saying that JS is inferior to Lua (Lua's great IK) isn't much of a productive comment—if they built their audience with JS, they wouldn't be pivoting to Lua because it has achieved superiority in the eternal language wars.

It was a provocative comment, sure, but I wouldn't say unproductive: others asked why Lua is superior, and several people answered, myself included.

Heartily agree that the same basic argument would apply if Roblox had started with JS! Lua isn't so much better than JS that it would be worth scrapping collective centuries of labour over.

But given that it is the better language, in my not-so-humble opinion, why on Earth would they switch?

I think the only rational way to argue superiority of Javascript over Lua, is to point to aspects outside of the language itself: larger install base, more libraries, bigger, more vibrant community, better tooling, and so on.

But I prefer to call that the ecosystem, and can confidently say that Javascript, alas, has the superior ecosystem. A pity, because Lua is the superior language.

I agree that lua is a good fit here, being a smaller and easier language to embed, with a much simpler runtime to boot.

But could you elaborate on why you think JS is an inferior language?

Lua is smaller, more consistent, and much more flexible with integration since it just C.

Debugging is built into the language in a simple way.

_G gets a table of all global variables

debug.getlocal allows you get any and all variables at each stack frame at that point including name, value and type.

You can even use debug.sethook to have a callback after every function, line, or instruction.

On top of all that you have LuaJIT which not only runs ridiculously fast, but can deal with dynamically loaded files incredibly fast. It even has a direct foreign function interface and allows you to use shared libraries directly without writing any more C or recompiling them.

Your last paragraph is the essence of why I chose Lua for my own projects, especially the FFI, but unfortunately doesn't apply to all of Lua. The divergence between Lua and LuaJIT is certainly not a strength.

The 'stock' Lua interpreter is very fast, very small (good for cache retention) and all its C functions are reentrant, so no global interpreter lock: those apply to both flavors.

As the tiniest of nitpicks, `_G` is the default home of the global environment, but it's just a variable. The correct way to get the environment is `getfenv(1)` or just `_ENV`, depending on your flavor.

> Debugging is built into the language in a simple way.

Lua debugging interface notably lacks breakpoints. You can simulate breakpoints by per-line hooks but it's not performant. Many popular JS engines including V8 do support performant breakpoints by bytecode patching, and Lua do have multiple patches doing the same (well, a common theme) but none is official.

Everything CyberDildonics said, and also:

Tables are immensely better than JS objects, in every way. I could entertain a separate array type in the abstract, but given what actually exists, I will take tables over the JS collection of objects, arrays, and maps, any day.

Extensible syntax, which is used to implement "objects", but also to, well, extend the syntax. Indexing, assignment, calling a table like a function, operator overloading: it all works the same way, and it all works. Metatables are superior to prototype chains, simple as.

Coroutines. I don't have the energy to write up my personal take on why these are the best baseline abstraction for cooperative concurrency in a dynamic language; one of these days I'll publish something so I can simply link to it.

Multiple return values. A language without variadic parameters and multiple returns is less dynamic than one which has them, if you're going to go dynamic, go all in.

Environments! This is one of those things which you might not need very often, but when you do: oh man, great to have. Number one thing I would carry over into the browser, where we really don't want to leak context, and JS fights against this constantly.

As minor desiderata, Lua's type coercion, the fact that 0 is truthy, a separate `..` concatenation operator, and optional semicolons which basically cannot bite you: Lua has no need for a separate `==` and `===`, and I consider that a strength. I also think the separate `null` and `undefined` is clunky, but that opinion isn't strongly held; Lua has considered adding an `undef`, and it's an interesting debate which I'm just barely on the "no" side of.

It's not easy to tell all your players that their games don't work anymore or that they cannot use coroutines. Regarding the second point, more concretely: they no longer can attach a function to a game object that defines its behavior over any length of time, and they no longer can call the sleep() function to suspend execution for a length of time.
True, there’s some pain involved for the developers. The migration could happen over time, like unity3d did by deprecating scripting with boo.
Lua does exceptionally well as a nice, lightweight language embedded in another project.