Most of the time there just isn't any need to make a difference between the three. There are basically just two common situations where you need to be able to tell the difference:
1. Telling apart optional arguments to a function that weren't given from arguments that were given and were NIL. Typically giving a default value for the argument is sufficient to avoid the problem, but you can also add an extra parameter that tells whether the argument was given.
(defun foo (&optional (x :default-value xp))
;; X holds the argument given by the caller, or the keyword
;; :DEFAULT-VALUE if nothing was given.
;; XP is T if the argument was given, or NIL if it was not given.
)
2. Finding an item that may be NIL. Functions such as GETHASH (which looks up a key in a hash table) usually allow you to specify a default value, and they will return a second value to indicate whether something was found. ;; Increment the value of KEY in TABLE, or if the key doesn't
;; exist, use 10 as a default, increment that and set the
;; result to the table.
(incf (gethash key table 10))
(multiple-value-bind (value existsp) (gethash key table)
;; VALUE holds the value if it exists, or NIL if it doesn't.
;; EXISTSP is T if the key exists, or NIL if it doesn't.
)
Notice that in CL additional return values can be ignored if you're not interested in them, so you only need to use MULTIPLE-VALUE-BIND when you actually need it. You could also wrap it in a trivial macro if you're using it a lot. (defmacro if-some-let ((var form) &body (then &optional else))
(let ((somep (gensym "SOMEP")))
`(multiple-value-bind (,var ,somep) ,form
(if ,somep ,then ,else))))
(if-some-let (val (gethash key table))
(format t "Found: ~a~%" val)
(format t "Didn't find.~%"))