Homoiconicity: Code as Data

Most programming languages draw a strict line between code (the instructions that tell the computer what to do) and data (the values the program manipulates).

But in some languages—most famously Lisp—this boundary is blurred.

The property that allows code to be represented and manipulated as data structures within the language is called homoiconicity.


1. What Does Homoiconicity Mean? Link to heading

The word comes from Greek roots:

  • homo- = same
  • icon = representation

So, homoiconic means “the code has the same representation as the data.”

In a homoiconic language:

  • The program’s source code is expressed in a fundamental data structure of the language itself.
  • This allows programs to generate, manipulate, and transform other programs in a natural way.

For Lisp, the basic data structure is the list (S-expression).

  • Code in Lisp is written as lists.
  • Lists are also a core data structure.
  • Therefore, Lisp programs can read, modify, and generate other Lisp programs as if they were just lists.

2. Example in Lisp Link to heading

Consider a simple arithmetic expression in Lisp:

(+ 2 3)

This is both:

  1. Code → adds 2 and 3.
  2. Data → a list containing three elements: the symbol +, and the numbers 2 and 3.

You can prevent evaluation and treat it purely as data using the quote operator:

'(+ 2 3)

This returns the list (+ 2 3) without executing it.


3. Manipulating Code as Data Link to heading

Because code is just lists, you can construct it dynamically:

(list '+ 2 3)
;; => (+ 2 3)

Now you can ask Lisp to evaluate this constructed list:

(eval (list '+ 2 3))
;; => 5

The program built a piece of code and then executed it.


4. Code that Writes Code Link to heading

A more powerful example:

(defun make-square (x)
  (list '* x x))

Calling (make-square 5) produces:

(* 5 5)   ;; just a list

If we evaluate it:

(eval (make-square 5))
;; => 25

We’ve written a function that creates new code and then executes it.


5. Macros and Homoiconicity Link to heading

The most famous application of homoiconicity in Lisp is the macro system.

  • A macro is a function that takes Lisp code (as lists) and returns new Lisp code (also lists).
  • The compiler then evaluates the transformed code instead of the original.

Example macro:

(defmacro square (x)
  `(* ,x ,x))

Now:

(square 7)
;; expands to (* 7 7)
;; evaluates to 49

This is different from functions because the transformation happens before execution. Macros exploit homoiconicity to let programmers extend the language itself.


6. Languages with Homoiconicity Link to heading

  • Lisp (Common Lisp, Scheme, Clojure) → canonical homoiconic languages.
  • Prolog → treats code and facts as terms (manipulable).
  • Julia (to some extent) → has Lisp-like macros, though its surface syntax isn’t pure S-expressions.