Introduction
Welcome back! In our last lesson, we completed the circle of Lisp's "code as data" philosophy by exploring how the eval function can execute a quoted list, treating static data as live code. This concept, known as homoiconicity, is central to Lisp's power.
So far, we've only used eval on lists we wrote by hand, like '(+ 1 2). But what if the code you want to run isn't known in advance? What if it needs to change based on user input or the current state of Emacs?
This lesson addresses exactly that. Your learning outcome is to construct a list that represents a valid Elisp expression and evaluate it dynamically. We will move beyond static, quoted lists and learn how to build expressions programmatically. This skill is the foundation for creating flexible, intelligent functions that adapt to their environment—the very heart of extending your Emacs workflow.
1. The Need for Dynamic Construction
Let's start with a simple goal: create a function that greets the current user. Emacs has a built-in function, user-full-name, that returns your name as a string.
In your *scratch* buffer, evaluate this:
(user-full-name)
You'll see your name (as configured in your OS) appear in the minibuffer.
Now, how could we use this in a message call? You might be tempted to try something like this:
;; This will NOT work as expected
(eval '(message (concat "Hello, " (user-full-name))))
If you evaluate this, you will get an error. Why? Because the single quote ' prevents the evaluation of everything inside the list, including the call to (user-full-name). The eval function is handed the literal list (message (concat "Hello, " (user-full-name))), and when it tries to execute it, it sees (concat ...) where it expects a string argument for message.
We need a way to construct a list where some parts are fixed (like the message symbol) and other parts are the results of evaluation (like the output of (user-full-name)).
2. Building Lists with the list Function
The most direct way to build a list from evaluated components is the list function. Unlike quote, which takes its arguments literally, list evaluates each of its arguments first and then collects the results into a new list.
Let's try our greeting example again, this time using list to build the expression.
;; 1. Construct the expression as a list
(setq my-expression (list 'message (concat "Hello, " (user-full-name))))
;; 2. See what the list looks like
my-expression
Evaluate the (setq ...) form. Then, place your cursor after my-expression and press C-x C-e. In the minibuffer, you will see the resulting list, which looks something like (message "Hello, Your Name!").
Notice two key things:
- We had to quote the symbol
message(i.e.,'message). If we hadn't, Elisp would have tried to evaluatemessageas a variable, which would likely cause an error. - The
(concat ...)expression was evaluated, and its result (the final greeting string) was placed into the list.
Now that we have a valid expression stored in the my-expression variable, we can evaluate it:
;; 3. Evaluate the constructed list
(eval my-expression)
Success! The greeting "Hello, Your Name!" appears in the minibuffer. You have just dynamically constructed and evaluated a valid Elisp expression.
3. A More Elegant Method: Quasi-quoting
Building lists with the list function and concat works, but it can get clumsy for more complex expressions. Lisp provides a more elegant and widely-used mechanism called quasi-quoting.
- A quasi-quote is initiated with a backtick (
). It acts like a normal single quote ('`), keeping most of the expression unevaluated. - An unquote is initiated with a comma (
,). Inside a quasi-quoted expression, it tells the evaluator to "unquote" or evaluate the very next expression and insert its result into the structure.
Let's rewrite our greeting example using quasi-quoting. It's much cleaner:
(setq my-expression `(message ,(concat "Hello, " (user-full-name))))
(eval my-expression)
This code does the exact same thing as our example with list, but it's easier to read. The backtick says, "I'm building a list template that looks like (message ...), but please evaluate the part marked with a comma and plug in the result."
This technique is fundamental to Lisp programming, especially for writing macros (which we'll touch on later in the course).
Learn Emacs Lisp in 30 Minutes
The following video gives a concise and clear explanation of quoting and quasi-quoting. It's a great way to see these concepts in action.
Watch the segment from 07:57 to 10:19. The narrator provides excellent examples of how quote prevents evaluation and how quasi-quote (with the backtick) and unquote (with the comma) allow for dynamic list construction.
As the video explains, this idea of constructing code structures is the essence of "code as data." You are literally building a list that happens to be valid, executable code.
ElispCheatSheet | Quick reference to the core language of Emacs
The ElispCheatSheet also offers a compact summary of this concept.
In the 'Quotes, Quasi-Quotes, and Unquotes' section, review the examples for quasi-quotes, such as (Hello ,name and welcome). This shows how you can embed computed values directly into a list structure.
4. Why This Works: A Quick Theory Refresher
Let's pause and connect this to what we know about evaluation.
GNU Emacs Lisp Reference Manual - Evaluation
Recall from the GNU Emacs Lisp Reference Manual that lists are evaluated based on their first element.
Briefly revisit the sections on 'Classification of List Forms' and 'Evaluation of Function Forms'. This is not new reading, but a quick review will reinforce the concept. The key idea is that for a list to be a function call, its first element must be a symbol that refers to a function.
When we construct (message "Hello..."), we are programmatically creating a function form. The Lisp evaluator sees it, recognizes message as a function, and executes it with the provided string argument. Both (list 'message ...) and (message ,...) are simply two different tools for achieving the same goal: manufacturing a valid function form to feed to eval.
5. Practical Example: A Dynamic Calculator
Let's solidify this with a function that builds and evaluates an expression. We'll create a function that takes an operator symbol (like '+ or '*) and two numbers, constructs the appropriate S-expression, and evaluates it.
(defun dynamic-calculator (operator num1 num2)
"Construct and evaluate an arithmetic expression.
Operator must be a quoted symbol, e.g., '+."
(let ((expression (list operator num1 num2)))
(message "Evaluating: %S" expression) ; %S prints the expression
(eval expression)))
Evaluate this function definition. Now, let's use it:
(dynamic-calculator '+ 10 5) ; ==> 15
(dynamic-calculator '- 10 5) ; ==> 5
(dynamic-calculator '* 10 5) ; ==> 50
Look at your *Messages* buffer after running these. You'll see the output from our message call, showing you the exact list that was constructed right before eval was called. This demonstrates the two-step process perfectly: first construct, then evaluate.
This simple function is a tiny glimpse into the world of metaprogramming—writing code that writes other code.
Conclusion
Today you've made a significant leap. You've gone from simply evaluating static code to dynamically constructing and then executing it. This is not just a theoretical exercise; it's a practical skill you will use to make your Emacs functions more powerful and adaptive.
Key Takeaways:
- To run code dynamically, you first need to construct it as a list.
- The
listfunction evaluates its arguments and collects the results into a list. It's a straightforward way to build an expression. - Quasi-quoting (with
`and,) is a more idiomatic and often more readable way to create list "templates" with parts filled in by evaluated expressions. - The goal of dynamic construction is to create a valid function form—a list where the first element is a function symbol—that can be passed to
eval.
Preview of the Next Lesson:
We have now concluded our exploration of the core Lisp paradigm: S-expressions, homoiconicity, quoting, and dynamic evaluation. You have the foundational tools to understand how Elisp code is structured and executed. In the next module, "Variables and Core Data Types," we will begin a systematic tour of the building blocks you'll use within these expressions. We'll start by taking a much deeper look at variables—how to define them, control their scope (local vs. global), and use them effectively in your functions.