4 ms·
The biggest Lua gotcha is that accessing a missing element is not an error. Requires a hack to distinguish “really null”.
by 0cf8612b2e1e 2y ago
The biggest Lua gotcha is that accessing a missing element is not an error. Requires a hack to distinguish “really null”.
- VWWHFSfQ 2y agomost often seen with something like JSON serialization where JSON null will result in the key being completely absent from the Lua table, thus re-encoding into JSON will be missing that key entirely instead of existing but with a null value. It's why almost every serialization library will have userdata values to represent "null" and things like empty arrays instead of empty objects. Still love Lua though.
- Dylan16807 2y agoActual Javascript is even worse, "undefined" and "not defined" are two different things, in addition to null.
- wruza 2y agoIt’s really “not exists”, although js’s own lexicon uses “define property” instead of “create property”. They don’t hesitate to “delete” it though. Apart from names, I find explicit models easier to work with and they allow for better designs. E.g. in Lua if t has mt.__index = {x=1}, then t.x == nil is non-representable, because t.x = nil deletes t.x and now .x proxies into index, which returns 1. In javascript, since existence is a separate thing, you can assign literally any value to t.x and it will not be proxied to a prototype until you delete t.x. This has a whole cascade of consequences that make your meta life easier. Undefined vs null is still a mess though, but not because the two exist, but because standard APIs use and return them mostly arbitrarily.
- Dylan16807 2y agoNo matter what system you use, there are always unrepresentable states like that. If you have javascript-style properties, then t.x cannot represent "the object lacks property x". I'm fine not having explicit property existence, and prefer throwing out the complexity needed to support it. Maybe I'm underestimating the usefulness, but I think in most situations I'd be happy with either setting to false or using a plain old initial value instead of __index, and in the rest of cases I could get the same effect by having __newindex disable __index for that key.
- wruza 2y agoT lacks x is represented by !Object.hasOwn(t, ‘x’), which lives on an explicit plane with in, delete, etc. While there is space for further argument, in my own practice js style is absolutely superior to lua minimal style when it comes to meta and also in general (although the latter is much less critical, I rarely do such distinctions in “user” code). The goal is not to find a universally infinite looped superidea, but to have one that is practical and no less. The best Lua way to do it imo is to not do it at all and use plain tables for data. Reinventing something that was omitted by design, and broken by that same design, is futile. Lua is as it is. You use it as is, or you fight with it, or you choose something else.
- Dylan16807 2y ago> hasOwn Okay, I guess I should have elaborated. I'm talking about the situation where you'd use "in" because you want properties on the prototype to count. HasOwn would not work there. There is no way to default via prototype to "yes it exists" but override that with "no it does not exist". > The goal is not to find a universally infinite looped superidea, but to have one that is practical and no less. I find nil plenty practical when I use Lua. Even when working in Javascript, I have never felt the need to override a prototype value with null or undefined.
- binary132 2y agoIn my opinion this is actually kinda awesome, because it’s as if every element in the domain maps to an element in the range, so there are no disjoint or undefined mappings in a table. It’s not how we’re used to thinking but when the logic is designed correctly it’s pretty useful and easy. Should accessing an absent record really be an _error_? Shouldn’t it just be a “None” or “Missing” option or something like that instead? It doesn’t seem to me like indexing a set with an absent key is really an error, it’s just not a value.
- giraffe_lady 2y agoThere are definitely different approaches with different tradeoffs. Lua tends to prioritize implementation simplicity so I would guess that's a factor here. Ideally yeah you'd return the Nothing side of an Option type but that's not representable in lua's type system. Returning an {:ok, data} tuple erlang-style is a pretty solid middle ground, and that is a common convention in lua with multiple return of ok, result. But throwing an error has advantages too. There is a potentially important semantic difference between an unused key and a null data. The difference becomes especially important when you go mixing data types IMO. I'm begrudgingly fine with a hashmap returning null for unset keys, but not with an array returning it for an out of bounds index. With lua's approach you can't easily differentiate these things and it does cause serious problems. Everyone hated this in php, I don't see why it's such a popular choice when lua does it.
- wruza 2y agoThe problem with bounds errors is that it brings little value for a correct program and there are better ways to help with correctness than a runtime error (but that transcends Lua-like langs). Looking at python, it’s more often and irritating to convert to .get() after an error than receiving an error and realizing it was helpful. Code that optimistically assumes non-null results rarely survives few next lines after getting nulls anyway.
- binary132 2y agoWhy would it be an _error_ to evaluate a function which is not defined for the given element?
- wruza 2y agoLua gotcha is not this. There’s no difference in “nil” vs “exists but nil”, because nil means “doesn’t exist” by definition. The gotcha is in # operator which when applied to a table may return incorrect “length” of its array part if it has holes in it. For example #{1,2,nil,4} can be 2 or 4. It happens because Lua arrays are not “just dicts with integer keys” as someone said above. A lua table impl has two parts: one for dict and one for array. # basically returns “impl(t).array.length”. This array part gets populated and depopulated heuristically creating non-determinated outcomes for #. This has nothing to do with bounds or existence tests. “Really null” is an attempt to have nil in an array, which Lua doesn’t by design.