Strongly typed nullable property without creating dummy object?

Suppose that I’m trying to create a queue of people, and I want to use an old-school linked list, where each person has a property pointing to the next person in the queue.

This is allowed:

A person has an object called the next in queue.
The next in queue of a person is usually nothing.

but it’s not ideal because the property “next in queue” isn’t strongly typed - it’s an object, not a person.

This is also allowed:

Mister Nobody is a person.
A person has a person called the next in queue.
The next in queue of a person is usually Mister Nobody.

but that’s not ideal either, because the property “next in queue” isn’t truly nullable - it uses the dummy object “Mister Nobody” to fake nullability.

This would be ideal:

A person has a person called the next in queue.
The next in queue of a person is usually nothing.

but it’s not allowed, because nothing isn’t a person.

So it appears that a variable or a property can either be more strongly typed than “object”, or it can be truly nullable, but never both.

Or is there a way to create a strongly typed nullable property that I’ve missed?

It is specifically disallowed by design.

There are tricks to force an assignment of nothing in the place of a strongly-typed value at run-time, but use at your own risk.

Perhaps Inadvisable Example
To decide which K is no (name of kind of value K):
	(- nothing -).

A person has a person called next in queue. [will default to yourself]

[since compiler can't know contents of I6 code, initial assignments must be made at run-time]
When play begins:
	repeat with P running through people:
		now the next in queue of P is no person.

Every turn:
	let P be a random person;
	if the next in queue of P is nothing: [OK to test for nothing]
		say "No existing assignment for [P].";
		let Q be a random person that is not P;
		say "Assigning [Q].";
		now the next in queue of P is Q;
	otherwise:
		say "Removing existing assignment.";
		now the next in queue of P is no person;
	say "Current assignment: [P] -> [next in queue of P]."
2 Likes

Do you happen to know the justification for this design choice?

In case anyone was wondering, the reason this works when

now the next in queue of P is nothing.
doesn’t- or even
now the next in queue of P is the I6-nothing-constant.
(the I6-nothing-constant is defined in the standard rules as translating into I6 “nothing”)

It’s because I7’s usual in-play type-checking is (almost uniquely) suspended when assigning an I6 inclusion as the result of a ‘To decide which…’ phrase.

The danger of this is that we’re under the hood ‘blindly’ assigning a number, which could (in theory at least) represent not a person but anything, or random junk, or nothing at all.

In the case of ‘nothing’ we’re assigning the number 0, which has a special meaning in the context of objects as meaning ‘no object’ or ‘nothing’, which is pretty safe.

We could choose to assign a specific non-person object:

An aspidistra, a rubber plant and a cactus is in the Lab. The aspidistra object translates into I6 as "aspidistra_object".The rubber plant object translates into I6 as "rubber_plant_object".The cactus object translates into I6 as "cactus_object".

To decide which person is a random plant:  (- ChoosePlant() -).
Include (-
[ ChoosePlant;
	switch random(3) {
		1: return aspidistra_object;
		2: return rubber_plant_object;
		3: return cactus_object;
];
-).

... 
... now the next in queue of P is a random plant.
...

which will compile happily but is getting more dodgy, albeit still strictly assigning a thing, if not a person

or just a random number:

To decide which person is a catastrophic fail: (- (random(10000)) -).

which will also compile happily but is almost guaranteed to lead to runtime unpleasantness.

Which obliquely answers the question of why in I7 is strong type-checking implemented generally, and more specifically to largely exclude nulls- it’s to protect the author (and reader) as far as possible from the likelihood of runtime problems due to coding errors terminating the story or even crashing the interpreter. The price being loss of some flexibility in how things are coded.

EDIT: It’s a bit like Visual Basic (or at least VB as it was when I last used it, a decade ago)- if you wanted a variable to possibly hold a null value, you had to declare it as a Variant type- which could hold any variable type or null, similar to how an object variable/property in Inform 7 can hold any kind of object, or nothing.

1 Like

The originator of null pointers refers to it as his billion dollar mistake.

That said, a line to cast nothing as an arbitrary kind like the one Otis provided is in a lot of my code.

1 Like

This seems overly fussy to me. What does “truly nullable” mean other than that the type has a unique null value that is different from every other value? That fact that nothing is represented as 0 in I6 code is not very interesting unless you’re writing I6 code yourself.

I generally create null objects with names like no-door, no-person, etc. This reads well in code (“if X is no-door…”).

1 Like

I suppose one could complain for example that list of people will include the no-person object but not a true null, or that repeat with r running through people will loop through no-person etc., which some might find a bit irritating…

Overall, I would tend to agree that using a ‘dummy null-object’ will suffice for most purposes, and if it doesn’t, just use an object rather than an object-derived kind.

1 Like

A valid point, but I found a way to mitigate that problem that leads me to prefer using strongly typed dummy null objects over using Inform 6 code to assign a true null to a strongly typed variable or property. You can do this:

An object can be null.
An object is usually not null.
Mister Nobody is a null person.

and when you want to loop over people, you can say ‘repeat with r running through not null people’ without needing to add code to your loop to check whether r is the specific object Mr Nobody.

1 Like

Of course, Inform also thinks a null object makes sense when as the default value for some uninitialized values (map region or matching key properties) or as a possible return value (holder of,first thing held by, next thing held after, location of for off-stage things…).

I think that implies a null value is too useful in Inform to go without. (I also think there’s room for reasonable people to disagree on what inferences one should or shouldn’t make here.)

And don’t forget blank values in tables!

1 Like