The Nil-Coalescing Operator they add is actually redundant.
Instead of their new fancy syntax:
print(message ?? "The message was nil.")
The idiomatic way would be:
print(message or "The message was nil.")
The or operator works just fine. (Edit: Note that this works because empty strings, lists and the number 0 are all truthy in Lua)
Generally not a big fan of adding additional syntax and operators for minimal gain.
I feel like most changes are nice to have but not really worth the disadvantage of not being able to use LuaJIT.
Honestly, the most interesting goal for me is is having a beefier Python-style standard library. Now, if we had that together with LuaJIT levels of performance that would be an absolute Python killer.
The only actual problem with Lua (that can't be fixed without forking) is that new variables are global by default but you can cope with that. Just have your linter check that you are always using the "local" keyword. Couldn't find if Pluto is fixing that.
Edit: Oh they provide a "let" alias for "local", case in point, little changes with not much value.
No it's not the same; `x ?? 'foo'` evaluates to `x` when `x` is not `nil` and to `'foo'` otherwise; `x or 'foo'` evaluates to `x` when `x` is not one of `nil` or `false`, and to `'foo'` otherwise.
This idiom seems to have been made widely popular by Perl folks, and while it has always been a good trick up your sleeve when you're 16 and want to impress the other kids, it's never been a good, correct way of doing things seriously, especially not across different scripting languages when every single one has its own Byzantine rules of what counts as truthy and what as falsey (like empty strings or empty lists). It is an especially pernicious idiom because of the widespread clout with which it tends to be transmitted; turns out it's one of the Falsehoods Clever Programmers Think They Know Better About. They don't.
i can see you are probably a polyglot, working in several languages with regularity.
it would certainly be frustrating to have to juggle these semantics as you switched back and forth.
however, languages are just that - they have their own meanings, and unfortunately they can't all be the same across languages (or they'd all be the same language).
different languages are different, and they have every right to be different, and I have every right to point out in whichever ways they differ. Personal tastes differ, too, and as far as I'm concerned I think that using 'shorthand Boolean coalescence' (I just made up that term) is a false convenience; when I write a condition that relies on a list being empty, ideally that should look like `if is_empty a then ...` according to my taste—explicit is better than implicit, and this is still concise.
Be that as it may but shrugging and sighing, like, "ah well, different languages will be different" and using that as an argument against a discussion of the merits and demerits of certain aspects of certain languages is not constructive, is it?
You want to learn the specifics of ?? means in this language, but not the boolean projections? It's one thing to be peeved that empty strings or lists are falsy when it's useful for them not to be, but this seems like barking up the wrong tree.
You almost never want to exploit, in a scripting language, the subtleties of the Boolean projections when writing the (un-) idiom `b = b or b_default`; it is more commonly a mistake made by script kiddies and hand-waving mavericks (i.e. People Who Finish First, a.k.a. People Who Never Write Proper Tests) that has become deeply ingrained due to the Look Ma How Clever effect. If `b` should be a Boolean with a default of `true` then `b = b or true` will quite obviously never allow it to be `false`. Rewrite that and put it in a row of such assignments as in `a = a or a_default; b = b or b_default`; c or c or c_default;` and it looses its fishy smell enough to pass as "OK, trivially no problem here".
I've indeed gotten tired by the entire idea of Boolean projections, yes, but that's not the point here, at least not almost always in practice.
I’m not clear on what the point of your response is, the comment you responded to is making an often overlooked point that certain subtleties in how languages implement constructs can create sharp edges, one of those being the null coalescing operator.
The edge of "false 'empty' values" is absent in Lua, indeed, but the `a = b or c`, more often used in the form `b = b or c`, has, in Lua, still the problem that it will evaluate to `c` if `b` is `nil` and when b is `false` which may or may not be what one wants—and often one wants it not. The idiom is commonly used to assign a proper value to a variable that has not been set to an explicit value, i.e. a variable that is `nil`. The assignment `b = b or true` will set `b` to `true` if it was `nil` or `false`; assuming allowable values for `b` are `true` and `false` and the default is `true`, `b` will then never keep the value of `false` and always be set to `true`.
I was just guessing here and didn't know about `//`; this in turn comes transparently from C as an enhancement of the C idiom `b = b || c`; it may have been a later introduction though (me learning Perl was way back in the (early?) 90s) or I missed it (not unlikely)
It’s been too long since I’ve played with Lua to recall whether this would also be the case, but in JS the nullish coalescing operator is useful over logical OR because it prevents false-y values (0, empty string, false) from being treated as nullish ones, a frequent source of bugs.
The number 0 and empty strings are considered truthy, so it isn't that bad.
return 0 or 42
> 0
Obviously the pattern doesn't work for checking for the existence of booleans as both nil and false are falsy but you are less likely to confuse that. So yes, the ?? would help a little bit but then again, you could just explicitly check for nil.
I feel like typing len(...) is not that bad, compared to how verbose "default if var_to_test is None else var_to_test" is (compared to "var_to_test or default"). But I agree it is nice -- I wish Python had the ?? operator.
len() may be either expensive or not possible to calculate for some collections where an emptiness check is trivial. For example a proxy for results from some paged API where you hold the first page initially. Empty check is immediate, len() may take hours.
Even though it would make the language a little more verbose, I would love if Lua had a 'global' keyword and declaring global/local was required. With that one change, I think Lua would have the best implementation of scope out of any language I've used.
Penlight[1] was supposed to offer python like batteries. It's kind of been abandoned by now, as has most of lua IMV. To me you still need luastd and a couple of other things (posix, lfs, socket, luasec) you can just fetch with luarocks and you're semi set.
Ideally a new Lua dialect would be capable of compiling to both Lua and LuaJIT. The language is effectively forked, with the fastest implementation (LuaJIT) and the official implementation (Lua) living in seemingly irreconcilable worlds.
Eh it's not just luajit and luajit didn't create that problem either. It's a symptom of lua actually succeeding at its design goal of being easily embedded as an extension language. A handful of incompatible runtimes are more popular than the most recent puc lua, including I believe the older official lua 5.2 released in 2011.
I've done a fair bit of professional lua development and I don't think I've ever written standalone up-to-date puc lua except maybe for some tooling & scripts. It's such a small language and used in such a way that the runtime, distribution method, and available APIs have much more impact on your use (and compatibility) than the version.
Virtually everyone shipping a lua environment is also shipping changes to it that make it a unique target, if only extensions to the standard library. This is why I think syntax layer-only approach like fennel's is the correct choice for improving on lua. It mirrors lua's runtime semantics exactly, and allows you to access the implementation peculiars on their own terms and so can just be run on top of any lua system.
Alternatively, Luau is a well-supported Lua variant with type checking and performance improvements, aimed more towards being a sandboxed embedded scripting environment.
That sounds more like a setmetatable(tbl,{__index = function () error"got nil" end}) variables are harder but you can make a wrapper function that errors whenever you get nil.
I cannot even imagine how pattern matching will work when it always returns nil, not to mention tables are full of nil sometimes, it would be an incredible waste to have a table full of false instead of nil.
oh, hmm, interesting. the concern is that if you store that nil in a variable (as opposed to using it immediately for control flow) you can't get it out again if loading a variable that has been set to nil continues to behave the same way as loading a misspelled variable?
i may be falling into the trap of trying to make language x more like language y that i'm more familiar with, but i think that if a language has one or more nil values, it's usually worthwhile to draw a distinction between a variable that has one of those values and a variable that doesn't exist at all. lua gets some simplicity out of conflating these situations, because it means it doesn't need a separate mechanism for causing variables to cease to exist (you can simply assign nil), and sometimes it can simplify your code by leaving initialization implicit. but i think that the result is that lua is a lot more bug-prone
languages that do draw a distinction between not existing, and existing with a null value, include smalltalk, ruby, c, c++, python, javascript, perl, python, tcl, java, the bourne shell (except perhaps very early versions), pascal, common lisp, scheme, golang, rust, octave, and most assembly languages. conflating the two is not completely unique to lua; awk, basic, and arguably fortran 77 do that, though the nil value they give previously nonexistent variables is a numeric 0 rather than Γ
the bourne shell and by default perl also allow reads from nonexistent variables with well-defined semantics, and perl and js also allow reads from nonexistent table slots (hash and array slots) with well-defined but sometimes startling semantics. but none of these have the behavior shared by lua, awk, basic, and arguably fortran 77, where there's no way to tell the difference between a nonexistent variable and a variable that has had the null value assigned to it
so i think that lua's design choice here should be regarded as an experimental innovation, like java's checked exceptions; and, specifically, a failed one
so there would be no problem with pattern matching returning nil and storing it in a variable
I'm not concerned, mispelling a variable can be fixed via using luacheck. Something I always do via tmux + editor + {entr -c}. that or using strict.lua which sets _G's metatable __index to error out. A limitation is disallows using ENV and you have to constantly be like local foo = _G.foo, but that makes your code a bit clearer I guess.
I think having a special datatype differing between nil and null would still render the code the same, instead of using if var==nil, you're just now doing if var==null, if you're using a variable after you know a function was used and its nil you know it returned null. you can always just use { or false } if you really need to differ between nil (and waste space), that or use assert()
Also my problem with pattern matching returning nil is it breaks usage of concatenative :, as in match:""match"":match""
So if you use concatenative objects with :, make sure you never return any other type but the object itself. its what allows lpeg operator overloading to not error out in the middle of it.
Perhaps even more relevant, it's already the name of a persistence library for Lua. Probably unmaintained at this point, but it's been around for decades (version 1.2 was released in 2004). Unless names are invented words, collisions are inevitable at this point.
One thing i like about lua, is that it is a very intentionally designed language with what it does or does not support. Many of the things pluto adss seem to be things that lua intentionally omitted as a design choice.
Odd syntax choices. The ones that stand out for me are "enum class" for scoped enums, and ":" as the delimiter in switch statements. It doesn't seem really well thought out.
I frankly find the whole idea of the switch statement unfortunate. The switch statement in C has a really understandable compiled representation: linear code where you can jump into in the middle. It was fine in 1970. But in 2023 I'd expect introduction of pattern matching instead, which generalizes the idea much better. Python pulled this off successfully. Java pulled it off successfully. Lua could, too.
I looked into the jump table optimization. The normal Lua version is here[1] and it uses the opcode as a index into a static array to jump to the label. The “faster” Pluto version is here[2] and it just uses a switch statement on the labels. I would naively assume that these would compile to the same code because the lua version is just manually creating the jump table and the pluto version is leaving it to the compiler. How could the compiler optimize the switch so that would outperform the manual jump table (by a decent margin)?
What is lacking is any space for community. No mailing-list or other place to discuss the direction of the project. No documentation expressing what is the overall reason or rationale behind the project, or who are the main contributors, how decisions are made, no roadmap.
There seems to be zero braking force on adding new features to the syntax and libraries. While I agree with 90% of them, I can find no discussion of why they were added, what arguments against adding were considered, how the decision was made, and by whom.
I have created my own (as yet unreleased) fork of Lua, adding some of these same features. I could abandon it and instead use (and contribute to) Pluto, but I am frightened by the mystery surrounding its origins, governance, and seeming lack of community input into the future direction.
Here's hoping you didn't want to alter the intermediate output directory away from "int" or change the CPPFLAGS for your own setup
I'm certainly no php ninja, but I'd put good money that a similarly homegrown bash build system would be about an equal number of lines of code but 1/10000th the WTF and not require an apt-get install before trying to contribute to the project
As a PHP dev, even I am perplexed by this. I normally just use Bash.
Then again, writing maintainable Bash is a pain so I don't totally hate it. Definitely have wrestled with a fair share of write-only Bash scripts where I was tempted to rewrite them in PHP.
I think berrylang shows a lot of promise now https://berry-lang.github.io/. The documentation has improved a lot and while it doesn't have a 'luajit' yet it has a lot of really interesting optimisation/reduction techniques.
Just tried it online¹ to see how it deals with a long standing problem² I have with Lua and I'm pleased to see actual improvement in the form of a warning:
script.pluto:1: warning: function was hinted to return string
but actually returns nil [type-mismatch]
1 | function foo(x: number): string
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ here
Unfortunately the underlying table was corrupted when the code ran and only
1 foo
was generated in the output. I would have preferred it to have refused to run at all (will need to look in the docs to see if there's a way to get that behavior with configuration). The purist in me wants more but the practical side of me appreciates that this is definitely an improvement over current Lua.
It's not "corruption" when you don't understand the semantics of the functions you're calling—ipairs stops at the smallest integer index that doesn't have a value. You can still find that the key 3 exists with pairs; you can't find the key 2, because it's not in the table.
The language just doesn't distinguish "key present with no value" in a table, which is frankly a bizarre thing to want with typechecking in the same breath.
You make a good point: since "pairs" returns all the values as it should, calling the table "corrupted" may be too harsh. It's been so long now I can't recall why the original code I found this problem in used "ipairs" instead of "pairs". It's simply not safe to use "ipairs" on a table containing a nil and the code that did that was wrong.
I think working with newer languages like Typescript have made me unsatisfied with languages like Lua which provide no clear way to prevent this sort of mistake, so I like that dialects like Pluto are showing ways Lua could be improved.
I genuinely think adding "classes, class inheritance" to Lua is a downgrade. There are many different systems, which you'll still encounter when using other Lua libraries anyway. Why add another?
Requiring C++17 to build is also a downgrade.
The lambda shorthand looks fine. Typing might be useful. But it says that's WIP so I'll have to try it out more than the examples. Default arguments, named arguments, the added general operators and safe navigation operators are generally useful and positive changes.
I would say that nearly every Pluto feature not marked as special on the https://pluto-lang.org/docs/Compatibility page would be a fine addition to mainline Lua. These are small quality-of-life improvements, battle-tested in other languages, with nearly zero new conceptual load.
Compiler message improvements would be great to port, too.
I see Pluto as a research project of sorts, trying new things. Trying incompatible and controversial things in such a project is the point.
Instead of their new fancy syntax:
The idiomatic way would be: The or operator works just fine. (Edit: Note that this works because empty strings, lists and the number 0 are all truthy in Lua)Generally not a big fan of adding additional syntax and operators for minimal gain.
I feel like most changes are nice to have but not really worth the disadvantage of not being able to use LuaJIT.
Honestly, the most interesting goal for me is is having a beefier Python-style standard library. Now, if we had that together with LuaJIT levels of performance that would be an absolute Python killer.
The only actual problem with Lua (that can't be fixed without forking) is that new variables are global by default but you can cope with that. Just have your linter check that you are always using the "local" keyword. Couldn't find if Pluto is fixing that.
Edit: Oh they provide a "let" alias for "local", case in point, little changes with not much value.