Skip to content

LISP - Data Types

A fundamental concept in Common Lisp is that variables are untyped, but values are typed. This means any variable can hold any type of data, but every piece of data (or object) in the system knows its own type. This provides the flexibility of a dynamic language with a rich, powerful type system.

All objects in Lisp belong to a hierarchy of types. The root of this hierarchy is T, which represents the supertype of all types. At the bottom is NIL, which is the type that has no members. Common Lisp provides a rich set of built-in types.

Here are some of the most essential, commonly used type specifiers:

  • Numbers: number, integer, fixnum, bignum, ratio, float, complex
  • Characters: character, standard-char
  • Symbols: symbol, keyword, null (the type of the object nil)
  • Cons Cells and Lists: cons, list, null (also the type of the empty list)
  • Arrays: array, vector, simple-vector, string, simple-string, bit-vector
  • Sequences: sequence (a supertype of list and vector)
  • Functions: function, compiled-function
  • Structures and Objects: structure-object, standard-object (from CLOS)

Lisp provides two primary functions for working with types:

  • type-of object: Returns a type specifier for the type of the object. The result can be implementation-dependent, so it’s best used for interactive debugging.
  • typep object type-specifier: This is a predicate that returns T if the object is of the specified type, and nil otherwise. This is the preferred, portable way to check an object’s type.
(let ((my-integer 42)
(my-float 3.14)
(my-string "hello")
(my-list '(a b c)))
;; --- Using type-of (for debugging) ---
(format t "Type of my-integer: ~s~%" (type-of my-integer)) ; Might be FIXNUM or (UNSIGNED-BYTE 62)
(format t "Type of my-string: ~s~%" (type-of my-string)) ; Might be (SIMPLE-ARRAY CHARACTER (5))
;; --- Using typep (for robust type checking) ---
(format t "Is my-integer an INTEGER? ~a~%" (typep my-integer 'integer))
(format t "Is my-float a NUMBER? ~a~%" (typep my-float 'number))
(format t "Is my-string a LIST? ~a~%" (typep my-string 'list))
(format t "Is my-list a SEQUENCE? ~a~%" (typep my-list 'sequence)))
Type of my-integer: (UNSIGNED-BYTE 62)
Type of my-string: (SIMPLE-ARRAY CHARACTER (5))
Is my-integer an INTEGER? T
Is my-float a NUMBER? T
Is my-string a LIST? NIL
Is my-list a SEQUENCE? T

Modern Practice: Optional Type Declarations

Section titled “Modern Practice: Optional Type Declarations”

While Lisp is dynamically typed, you can provide optional type declarations. These declarations serve two main purposes:

  • Performance: They give hints to the compiler, allowing it to generate more specialized and faster machine code by avoiding generic operations and runtime type checks.
  • Error Checking: A modern compiler can use these declarations to warn you at compile time if you are passing the wrong type of data to a function.
;; This function calculates the area of a circle.
;; We declare that the radius must be a floating-point number.
(defun circle-area (radius)
(declare (type float radius))
(declare (optimize (speed 3) (safety 0))) ; Ask compiler to prioritize speed
(* pi radius radius))
;; Calling it with the correct type works and is fast.
(print (circle-area 10.0))
;; Calling it with the wrong type might signal a compile-time warning or a runtime error,
;; depending on the implementation and safety settings.
;; (circle-area 10) ; This could be flagged.
314.15927