Introduction
In our last lesson, we focused on quote and its shorthand ', discovering how it allows us to stop the Lisp evaluator in its tracks and treat code as data. We briefly labeled quote as a special form, a term we used to explain why it behaves differently from a function like +.
Today, we will dissect that term. This lesson's goal is to explain the difference between a function and a special form, using quote as our primary example. This is not just a theoretical exercise; understanding this distinction is fundamental to grasping Elisp's architecture. Special forms are the foundation upon which control structures like conditionals and variable bindings are built. Mastering this concept will clarify why so much of the Elisp you read and write is structured the way it is.
1. The Standard Rule: Function Evaluation
Let's begin by formalizing the default behavior of the Lisp evaluator. When the evaluator encounters a list that is not empty and does not begin with a special form keyword, it treats it as a function call.
The rule for evaluating a function call is simple and strict:
- Identify the function to be called (the first element of the list).
- Evaluate every one of its arguments, in order from left to right.
- Apply the function to the results of the argument evaluations.
Consider this expression in your *scratch* buffer:
(+ (* 2 5) (- 10 3))
Evaluating this (C-x C-e) gives you 17. The evaluator performs these steps:
- It sees it needs to execute the
+function. - It evaluates the first argument,
(* 2 5), which results in10. - It evaluates the second argument,
(- 10 3), which results in7. - Finally, it applies the
+function to the results:(+ 10 7), yielding17.
This "evaluate-all-arguments-first" rule is the defining characteristic of a function. For a more formal description of this process, the official GNU Emacs Lisp Manual is the definitive source.
GNU Emacs Lisp Reference Manual - Evaluation
This section from the GNU Emacs Lisp Reference Manual precisely describes how a function call is evaluated.
Please read the short section titled 'Evaluation of Function Forms'. Pay close attention to the statement that the 'remaining elements of the list' (the arguments) are evaluated from left to right before the function is called.
2. The Exception: Special Forms
Now, what happens when this strict evaluation rule is not what we want? In the previous lesson, we saw that (car '(+ 1 2)) works, but (car (+ 1 2)) fails. A function-based approach cannot solve this, because the inner list (+ 1 2) would always be evaluated to 3 before car could ever see it as a list.
This is why Lisp has special forms.
A special form is a primitive operator that is "special" because it does not follow the standard function evaluation rule. Instead, each special form has its own unique rules for how it handles its arguments. The Lisp evaluator is hard-coded to recognize a small set of these fundamental operators (like quote, if, let, defun) by their name.
When the evaluator sees a list, the very first thing it does is check if the first element is the name of a special form.
- If yes, it uses the unique evaluation logic for that special form.
- If no, it proceeds with the standard function evaluation rule we just discussed.
Let's turn to the manual again for the official definition.
GNU Emacs Lisp Reference Manual - Evaluation
These sections from the manual formally define special forms and distinguish them from function calls.
First, read the two paragraphs under 'Classification of List Forms'. This explains that the first element of a list determines its kind (function, macro, or special form). Then, read the first paragraph under 'Special Forms'. This is the key definition: a special form's arguments are not all evaluated, and each has its own rules.
3. Case Study 1: quote
With this new definition, let's revisit quote.
- As a special form,
quoteis recognized by the evaluator. - Its unique evaluation rule is the simplest possible: Do not evaluate the single argument. Return it as is.
When the evaluator sees (quote (+ 1 2)), it identifies quote as a special form and immediately invokes its special rule. It bypasses the standard "evaluate-all-arguments" procedure, grabs the unevaluated list (+ 1 2), and returns it. This is precisely why quoting works and is the most direct illustration of a special form's power.
4. Case Study 2: if for Conditional Logic
To prove that this concept is broader than just quote, let's examine another crucial special form: if.
The if special form provides conditional execution. Its syntax is (if CONDITION THEN-FORM [ELSE-FORM...]).
Its special evaluation rule is:
- Evaluate the
CONDITIONargument. - If the result is not
nil(i.e., true), then evaluate and return the result ofTHEN-FORM. TheELSE-FORMis never touched. - If the result is
nil(i.e., false), then skipTHEN-FORMand evaluate and return the result ofELSE-FORM. TheTHEN-FORMis never touched.
Try this in your *scratch* buffer:
(if t
(message "Condition is true!") ;; This will be evaluated
(message "Condition is false!")) ;; This will be ignored
You'll see "Condition is true!" in the minibuffer. Now try this:
(if nil
(message "Condition is true!") ;; This will be ignored
(message "Condition is false!")) ;; This will be evaluated
You'll see "Condition is false!".
This selective evaluation is impossible for a regular function. If if were a function, it would have to evaluate all its arguments first: t, (message "..."), and (message "..."). This would result in both messages being printed before the if logic could even decide which one was correct.
Control flow constructs like if, cond, and while must be special forms to prevent evaluation of code branches that shouldn't be executed.
5. Summary: Function vs. Special Form
The distinction can be summarized in a table:
| Aspect | Function | Special Form |
|---|---|---|
| Evaluation Rule | Evaluates all arguments before execution. | Has a custom evaluation rule; may not evaluate all arguments. |
| Purpose | To compute a value from its inputs (arguments). | To provide fundamental language structure (control flow, binding, quoting). |
| Origin | Can be user-defined (defun) or built-in. |
A small, fixed set of primitives built into the Lisp core. |
| Example | +, car, concat, message |
quote, if, let, setq, defun |
This clear separation is a hallmark of Lisp's design. A small, robust core of special forms provides the language's syntax and control flow, while everything else is built on top using functions.
To solidify this, let's read one final, concise explanation that contrasts these two worlds.
[PDF] Introduction to Lisp and Emacs
This PDF provides a very clear summary of the two evaluation paths the Lisp interpreter can take.
Please read section '3 Special Forms' (page 11-12) and then '7 Further Exploration of Lisp' (page 22). The first section reinforces the idea of custom evaluation rules, and the second provides a fantastic summary that directly contrasts the evaluation of a regular function call with that of a special form.
Conclusion
You have now moved from using quote to understanding the fundamental principle that makes it work. This distinction between functions and special forms is a core concept in Elisp.
Key Takeaways:
- Functions follow a strict evaluation rule: evaluate all arguments first, then apply the function to the results.
- Special Forms are exceptions. They are primitive operators with unique evaluation rules that give them control over if and when their arguments are evaluated.
quoteis a special form whose rule is "do not evaluate the argument."ifis a special form whose rule is "evaluate the condition, then evaluate only one of the subsequent branches."- This distinction allows Lisp to build fundamental control structures directly into the language, rather than having them as separate statement types like in many other languages.
Preview of the Next Lesson:
We've mastered quote, the tool to prevent evaluation. It's only natural to ask: what is its opposite? In the next lesson, we will explore the eval function. It does the reverse of quote: it takes a piece of data (like a quoted list) and explicitly tells the Lisp evaluator to execute it as code, completing the "code as data" circle.