Add undefined value

In lua it is currently impossible to discriminate between a variable with no value and a variable that doesn’t exist (*except in one specific case).

The properties of this value should be:

  • It is falsey
  • It holds space in a table
  • It is non-boolean

The goal here is to transition nil into “this doesn’t exist”, and undefined will take on the role of “this exists, but it isn’t set”.


Consider a car object:

car = {
   wheels = 4,
   speed = 20,
   driver = nil
}

In roblox right now, it is impossible to tell the difference between “the car current has no driver” and “the car has no support for a driver”.

With undefined, we can make this clear.

car = {
   wheels = 4,
   speed = 20,
   driver = undefined
}

Now it is clear that a driver can be there, but there isn’t one there right now.

33 Likes

EDIT: correction - Lua is strongly-typed but dynamic, which is what allows for value types to dynamically change.

Although I also often deal with undefined values, I’m not certain just how necessary an undefined value type would be due to Roblox’s weak-type dynamic coding structure. I believe undefined values are necessary in strongly-typed languages such as Java and Python because their variables are strictly declared (e.g. integers must remain integers, and no numbers, including zero should not be considered a transitional value).

Regarding the car scenario, I would just assign an additional value so the game knows whether or not to read from the target value yet or at all, e.g. drivable = true/false. This solution should also be strong-type language compatible.

If you’re willing to assign a different value type to the target value, I would set the driver value to false. When it comes to reading the data, if the driver value is false, then it should be considered undrivable; otherwise if it is nil, then it is ready for a driver.

TL;DR for most transitional variables I’ve dealt with, using any other value type is nearly as practical as undefined, otherwise use a supplementary value to define its state.

5 Likes

One situation where something like an undefined symbol is desirable is when you are consolidating a state with a delta and you want to specifically mark “delete this variable”.

e.g. your state is:

{
   a = 1,
   b = 2,
   c = 3
}

Let’s say I want to change b and remove c, I can do that conveniently when I have an undefined:

local change = {
   -- don't change a
   b = 10, -- change b to 10
   c = undefined -- remove c
}

This can’t be done with just nil because the key would not exist in change if set to nil. Most libraries currently get around this by defining their own undefined symbol as a pointer to a common object (e.g. newproxy() stored in a library member).

6 Likes

The nil/undefined dichotomy is also why persisting mixed tables to DataStores doesn’t work/is a pain. There is no way to distinguish between unallocated vs. non-existent.

Relatedly, if we are talking about special sentinel values, Lua has bad support for NaN.

I had a currency bug where I divided by zero and because x/0 doesn’t cause a script to halt or throw like it would in almost any other language, I added adding NaN to all my player’s money balances.

This then became a pain to fix because there is no Lua library call to test for NaN-ness.

In Lua, to test for NaN-ess, you do this awful thing:

if n ~= n then print("n is NaN!") end

Please add more standard library support to Roblox Lua. Thanks for coming to my TED talk.

7 Likes

To be honest, I feel it’s already possible to achieve this example of undefined usage by utilizing an unused value type for undefined (in my proof, the string “undefined”) and doing the appropriate check for it when consolidating tables.

local targetTable = {
   a = 1,
   b = 2,
   c = 3
   }

local mergeTable = {
   -- "a" is nil so there is no change
   b = 10, -- Change "b" variable from 2 to 10
   c = "undefined", -- Remove "c" variable
   d = 12 -- To test adding a new variable, add "d"
   }

--== CONSOLIDATE ==--

for key,value in pairs(mergeTable) do
   -- For the sake of this proof, let's call this "delete" flag "undefined"
   if value == "undefined" then
      -- Delete key in targetTable
      targetTable[key] = nil
   else
      -- Set key in targetTable
      targetTable[key] = mergeTable[key]
   end
end

--== TEST ==--

print(targetTable)

--== RESULT ==--

--[[ Should print:
["a"] = 1,
["b"] = 10,
["d"] = 12
]]

I’m totally open to supporting the addition of undefined, I’m not here to argue it as if I have something against it. I’m just convinced that nil is a sufficient undefined as long as one takes advantage of Lua’s weak-type dynamic variable structure.

Wouldn’t another value be needed if the state wanted to represent setting the value to undefined?

local change = {
	-- don't change a
	b = undefined, -- set b to nil (aka remove b)
	c = undefined2 -- set c to undefined
}
-- and then there needs to be a value to represent undefined2...

I believe that a better solution for this would be allowing nils to be values in tables, but this has its own set of problems.

__metatable has a similar problem that it doesn’t support a way to force getmetatable to return nil, would undefined be used for this purpose?


This description seems like variables would by default get undefined, since they exist but don’t have a set value.

Since undefined is supposed to be similar to nil but with more uses, will it stop generic for loops? Will it be valid as a table key? If it isn’t a valid table key, what will next(t,undefined) do? Will it get a special type, or will type(undefined) be "nil"? Will the length operator consider it to be a border, or will it handle it like a regular value?

I don’t think the solution is to add a new type for a new falsy value. Adding a way to overload truthiness with a metamethod would be a better solution IMO, it’d allow for implementing several special falsy values.

2 Likes

As much as I’ve been tempted to ask for this very thing in the past, I have to disagree with the suggestion. One non-value is MORE than enough. If one wants to model a table key which exists but does not have a value, one could use Option/Maybe types. Adding undefined essentially makes one or two things easier, but then adds complexity to ALL CODE EVERYWHERE.

For example, a lot of code that used to look like: if value == nil then now becomes: if value == nil or value == undefined then. Then someone asks for standard library functions to simplify their code and you end up like PHP with all sorts of helpers just to determine if there’s a value in a variable (isset(), empty(), is_null(), etc).

It’s better to simply design solutions specific to the particular problem.

4 Likes

To solve what problem? It’s not relevant for the use cases I can think of, no.

2 Likes

This is what falsey checks are for. if not value then

6 Likes

Which is precisely why false is already a workable placeholder value in most cases for being able to pass falsey checks. I use it in many systems such as slot-based inventories to retain keys in dictionaries while indicating that the slot is available.

I simply don’t see a significant advantage in utilizing undefined. All the use cases presented in this thread are solvable with existing methods.

2 Likes

false is an opiniated placeholder value because it is typed and has use cases / meanings beyond “undefined”.

The request is to add a symbol of which the sole purpose is to identify a nil-like value that can be encoded and read properly when stored in dictionaries. That way everyone can be consistent and doesn’t need to arbitrarily select some other value (e.g. boolean false) to stand in for that.

4 Likes

I concur with human readability as a reason for having undefined, I would hope to see this reflected in the OP since the primary argument up to this point has only been “it is impossible to distinguish between nil and unset values,” which I disagree with. Otherwise, I still consider it to be an extraneous value type with a purpose that sufficient conditional checks can already compensate for.

1 Like

Bump. It’s such a pain working with dictionaries and external servers.

It wouldn’t break older code, please add it in the Luau roadmap

Support. Obviously useful for cases like in the original post, but this would also make an “object / instance” attribute possible.

You could just define what’s possible or not with type system or proper documentation.
And even if we had undefined, these problems still need typing or docs to solve.

  1. We wouldn’t know the type of a field just by looking at the value if it’s undefined (even the example the og gave doesn’t let us know what the type of driver is)
  2. We don’t know if a field can be nil or undefined, which is worse then only having nil.

Also, it could potentially break old codes. And it would take a lot of effort to make a new primitive type. So I don’t think it’s worth it to add undefined.