With a functionality including what @Blockzez said would be great, which I have a proposal for below, and I’d honestly be all for something like this.
Also, what @Neutron_Flow suggested can easily be done like so (which is safe for the none type too):
abc ~= false and abc
If abc equals false, the result will be false and the expression ends, if abc is nil, none, or anything else, the result will be simply abc
.
With what @Blockzez said, I don’t think a new metatable is a good idea, I think it makes most sense if the operator behaves the exact same as indexing, it just greedily “cancels” the expression. (Keep in mind, JavaScript actually also has a metatable equivalent, Proxy()
which doesn’t have any different effects from the chaining operator vs normal indexing afaik)
I think it makes sense logically that this chaining effectively resolves to a none type (rather than nil) if any of the full expression fails. (abc?).cde
vs (abc)?.cde
would error on the first one and the second one would behave equivalently to abc?.cde
. This would not cause any issues, as you can simply do something like the following if you happen to need nested behaviour like that: (abc?.cde or efg)?.ghi
.
As for my reasoning why it should be the none type instead of the nil type, converting it to a nil type can be confusing in the case of tuples, and since <none>?
would resolve to nil
based on what others are saying.
And, of course, since this is still normal indexing, game.IDontExist?
will still cause an index error for an invalid property rather than resolving to none which I would expect, the only difference here is that when the optional result is nil or none the entire expression resolves to nil or none.
In this case when I say expressions by the way, nil? or abc()
has two “expressions” on the left and right, and content within parentheses are also expressions:(abc) or (cde or (efg))
The following would then easily be possible without changing behaviour (and can be represented in lua already):
someFunction?(123, value?)?
You can break this expression down into a “universal” equivalent like so (which would be pointlessly slow, of course):
(function()
if someFunction then
-- Yeah, this isn't correct syntax, so, you'll have to 1v1 me to be allowed to be upset
local result, ... = (someFunction(123, (function()
if value then
return value
end
end)()))
return (function()
if result then
return result
end
end)(), ...
end
end)()
Funnily enough, I use behaviour like this already in my code, though, when possible I always use something like abc and abc.cde or nil
.
This would actually allow us to freely create the none type as well, which would be really cool for testing purposes since nil
and none can be differentiated between and often are in C code. For example, some Roblox functions expect something non none, such as nil
. A really good example is modules, modules expect one single value type to be returned that is non none.
You would expect that this would error:
-- Module that errors
return nil?
But this would not:
-- Module that works
return nil
You can actually test this like so:
-- Will error since the returned type is none, not nil or anything else
return (function()
return -- This is actually redundant, just added it for demonstration purposes
end)()
Another example of this is console output:
print(nil?) -- (nothing)
print(nil) -- "nil"
And, of course, if you really needed a non none type this honestly isn’t so bad:
abc? or nil