4

The Common Lisp spec states that nil is the name of the empty type, but I've never found any situation in Common Lisp where I felt like the empty type was useful/necessary. Is it there just for completeness sake (and removing it wouldn't cause any harm to anyone)? Or is there really some practical use for the empty type in Common Lisp? If yes, then I would prefer an answer with code example.

For example, in Haskell the empty type can be used when binding foreign data structures, to make sure that no one tries to create values of that type without using the data structure's foreign interface (although in this case, the type is not really empty).

  • Are you asking whether nil can be dissociated from the concept of the empty type, or are you asking whether the language can do without nil completely? – paul Jul 18 '14 at 13:06
  • @paul The former - my question is not about nil, it's about the empty type. I'm just wondering why anyone felt that it was necessary to add the empty type to the Common Lisp language. I can't think of any situation where it's needed/useful. – Pedro Rodrigues Jul 18 '14 at 13:36
6

Type declarations and static analysis

Since no value typed as NIL can exist, the use of the NIL type is mostly relevant for static analysis. T and nil respectively represent the top (⊤) and bottom (⊥) types and might be used when inferring the type of expressions. Consider the following example:

(defun the-thing () (length (1+ (read))))
  • The type of (read) is T, because if reading succeeds, the returned value can be anything.
  • The type of (1+ (read)) is necessarily NUMBER, because the returned value exists only if no error is signaled. And if this is the case, we know from the type analysis of + that the result is a number.
  • The type of (length (1+ (read))) is NIL, because length returns a value only for sequences. Giving a number is guaranteed to signal an error, and thus the possible values returned by the function is represented by the empty type (an empty set of values).

This kind of analysis is not only theoretical, it actually happens with SBCL:

* (defun the-thing () (length (1+ (read))))
; in: DEFUN THE-THING
;     (LENGTH (1+ (READ)))
; 
; caught WARNING:
;   Derived type of (+ (READ) 1) is
;     (VALUES NUMBER &OPTIONAL),
;   conflicting with its asserted type
;     SEQUENCE.
;   See also:
;     The SBCL Manual, Node "Handling of Types"
; 
; compilation unit finished
;   caught 1 WARNING condition

THE-THING

Moreover, here we can see the derived type of THE-THING:

* (describe #'the-thing)

#<FUNCTION THE-THING>
  [compiled function]

Lambda-list: ()
Derived type: (FUNCTION NIL NIL)
Source form:
  (SB-INT:NAMED-LAMBDA THE-THING
      NIL
    (BLOCK THE-THING (LENGTH (1+ (READ)))))

The NIL type means: this functions will not return successfully. For example, in SBCL, the error function has the following declared type:

(FUNCTION (T &REST T) NIL)

... because it never returns a value but signals a condition. You can use NIL in function type declarations explicitely if you want. By the way, please notice that SBCL only gives a warning and still compiles your code: you can call the function and it will trigger the dynamic checks that will lead you to the debugger, etc.

NIL implies the absence of "normal" termination. If the body of your function is (loop), the return type is also NIL. But sometimes you can return normally but you do not have any value to return (like void in C). That is why (values &optional) is more appropriate as a type declaration when you return no value successfully. This is done by using the (values) expression in your code. The &optional keyword in the type declaration is necessary because the type (values) would allow you to return more values (see comments).

Is nil necessary?

Some implementations might not care about nil. Since you wonder if we could remove it without causing any harm, maybe trying to remove the nil type from an implementation could be an nice experiment, to see how much does effectively break in practice.

Foreign types

Regarding foreign types, I doubt it can be used like in Haskell, while conforming to the spec, because:

  1. the behavior is undefined if a value does not match the declared type, and
  2. there is already a type for foreing values in CL (but maybe I did not understand exactly what you mean with foreign types and Haskell).

This is purely speculative, but maybe a variable (or an array) of type nil allocated by a CL runtime could be handled to some foreign code (using implementation-specific means). This is not how it is done in CFFI.

The case of nil arrays

I don't know why (maybe just because there is no check) some implementations like SBCL allow arrays of elements of type nil, defined as follows:

(type-of (make-array 10 :element-type nil) )
=> (SIMPLE-ARRAY NIL (10))

However, one can't access an element of such an array:

(aref (make-array 10 :element-type nil) 5)

The following type error is caught: "The value 5 is not of type (MOD 4611686018427387901)." Moreover, the backtrace contains the following line, where the actual content of the array is seen containing random values:

(AREF "¿[¦^D^P^@^@^@×[" 5)

Accessing uninitialized arrays has undefined consequences (see comments). Consequences are undefined even if the actual data is only referenced and not effectively used for its content, as in:

(LENGTH (LIST (AREF (MAKE-ARRAY 1) 0)))

The example is taken from the "Issue UNINITIALIZED-ELEMENTS Writeup".

I see no reason to allow the creation of arrays of type NIL in the first place, apart maybe from allocating (useless?) memory. As said above, maybe there is an implementation-specific, totally not portable way for some lisp to use such arrays, and pass them to foreign functions, but I doubt it.

Finally, in Maclisp's manual (note that Maclisp predates Common Lisp), it is said that NIL arrays can be used as arrays of type T but make the garbage collector behave as if no objects were referenced. However, here is what the manual says about them:

Type NIL arrays are discouraged even for those who think they know what they are doing.

  • 1
    I'm not confusing. The way to declare you return no values, is correctly done with (values &optional). With nil, you declare it returns a first value of type nil, which is impossible and the implementation could signal an error or at least a warning right on the declaration. If it was null, the only valid first return value would be nil. There is no portable way to declare that a function doesn't return. – acelent Aug 21 '14 at 11:09
  • 1
    Just for the record, the rationale behind the need of &optional at the end of a compound values type declaration to declare no further return values has been discussed in one of SBCL's mailing lists. Basically, the presence of lambda list keywords indicates the amount of parameters to a function that would be correctly called with the returned values as if by multiple-value-call. The lack of them allows further return values (of type t). The consequence is that (values &optional) is the only reliable way to declare no return values. – acelent Aug 21 '14 at 11:25
  • 1
    (1) I don't know, some SBCL error functions are already declared as such, so I guess you can trust SBCL will not error on such declarations. (2) The only reliable, portable way to say that a function returns no values is that, but there is no portable way to say that a function doesn't return at all. (3) Declarations may be completely ignored, but they may also be completely trusted, so it goes that it's implementation dependent, hence you can't trust it won't error, but you can't trust it will error either, and it might error either at the declaration point or during compilation. – acelent Aug 21 '14 at 15:36
  • 1
    I get SBCL's point of view, that the only way to satisfy a nil return type is to not return at all, but it's a deduction or even a choice, since it's undefined behavior (versus clearly meaning the function can't return anything) to deal with a return value that is not of the declared type. So if a compiler can prove such a function may return, it could error on definition, on compilation and/or on load. – acelent Aug 21 '14 at 15:45
  • 1
    Consequences are undefined even if the actual data is never effectively used, as in: -- But in that case, the data is effectively accessed, used to create a list of one element. But (length (list (make-array 10 :element-type nil))) should still reliably return 1. – acelent Aug 21 '14 at 16:06
2

One practical use is to generate errors when the implementation takes your declarations into account. It might be specially useful in machine-generated and/or debug code, or when you want to turn e.g. a dynamic variable obsolete and at some point declare it of type nil, so reading from, writing to or binding it will signal an error (or is otherwise undefined behavior), but checking for boundp will not.

Another use is if you can only pass a type specifier to e.g. a filtering API, so nil (no value is of type nil, nil is a subtype of any type) can be as useful as t (any value is of type t, t is a supertype of any type), but the other way around.


EDIT: It also serves as a base type specifier, as the types (or) and (member) are equivalent to the type nil.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.