There's a lot of approaches that might work; using something other than a list
for your collection is just one (although it is often appropriate).
If you just want option types that you can use with lists, the most direct way
to do that is by encoding None as NIL and Some x as (list x), which still lets
"no value" be false, makes "some value" true even for the empty list, and
pairs nicely with pattern matching libraries like Optima (which people
yearning for option types might also be interested in):
That seems cleaner to me, too: if you want to use the empty list for "no
value", then why wouldn't you use a list containing the value you want for
"some value"? This also scales to more than 1 optional value, and doesn't
require any special casing for lists, if you're thinking of them as values
like any other; the NIL thing doesn't even come into it. Maybe this sort of
confusion comes from how null works in languages like Java?
It might also make sense to represent the absence of a value by just not
assigning a value. For example, CLOS instances won't bind their slots to
anything by default, so trying to read them will signal UNBOUND-SLOT. You can
check if it's bound beforehand with SLOT-BOUNDP, and CLOS does differentiate
between a slot not being bound and a slot not existing at all. If you're
working on something interactively and UNBOUND-SLOT gets signalled, by default
execution will pause, and Lisp will offer strategies for resolving the issue,
like entering in a value to use instead for just this call, entering in a
value to store in the slot for later use, just aborting the computation, etc.
That's a pretty nice experience (and just the tip of the iceberg) that people
coming from more static languages might not even think about.
If you're returning a value to someone, maybe it would be best to work like
GETHASH does and return two return values, the first either a value pulled
from the table or a default, and the second a boolean indicating whether the
key was present or whether you got the default. Because Lisp has multiple
return values (not like Python or Ruby where you're really just returning a
sequence and then destructuring it), callers that are fine with the default
and don't care if it was actually present can just use the primary return
value, and since the default is provided when you call GETHASH, you can also
do nice things like (incf (gethash key hash 0)).
There's other ways too, of course, but it's impossible to say what would be
the most appropriate solution without knowing more about the problem. (eq nil
'()) returning T is not a bug, so the issue must be higher up the call stack.
If that seems like a dealbreaker to someone, they're probably just looking at
it in isolation and not thinking in terms of Lisp as a whole.