Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 28

Conditional Logic with `cond`

Recommended resources

Introduction

In our previous lesson, we established the building blocks of decision-making in Elisp with if, when, and unless. These are excellent tools for binary choices: if this, then that; otherwise, do something else. But what happens when a decision isn't a simple fork in the road, but a crossroads with multiple paths? While you could construct a tangled web of nested if statements, Elisp provides a much more elegant and readable solution.

This lesson directly addresses the learning outcome: Use cond for multi-branch conditional logic. We will explore how cond acts like a "switch" or a series of "if/else if/else" checks, allowing you to test a sequence of conditions and execute the code associated with the first one that proves true. This structure is fundamental for writing clean, comprehensible functions that need to handle a variety of cases—a key skill for building the sophisticated, custom tools you envision for your Emacs workflow.


1. From Two Paths to Many: The cond Special Form

Imagine you're sorting mail. You don't just have two piles (e.g., "mine" and "not mine"). You might have piles for bills, personal letters, magazines, and junk mail. The cond special form is designed for precisely this kind of multi-category sorting.

The basic structure of cond is a list of clauses. Each clause is itself a list containing a test and one or more expressions to execute if that test is true.

(cond (TEST-1 BODY-FORM-1 BODY-FORM-2 ...)
      (TEST-2 BODY-FORM-3 BODY-FORM-4 ...)
      ...
      (TEST-N ...))

Elisp evaluates TEST-1. If it's non-nil (true), it executes the body forms of that clause and stops, returning the value of the last form. If TEST-1 is nil, it moves on to TEST-2 and repeats the process. It continues until a test succeeds or it runs out of clauses.

This image provides a classic, clear example of cond in action.

Example of COND in Lisp
This code defines a function `compare` that takes two numbers, `x` and `y`. It uses `cond` to test if `x` is greater than `y`, less than `y`, or if they are equal, printing a corresponding message.

In the compare function shown, cond first checks if (> x y). If true, it prints "x is greater than y" and the cond expression is finished. If not, it proceeds to the next clause, checking if (< x y). If that's true, it prints its message and stops. If neither of the first two tests passes, it means x must be equal to y.

Notice the final clause: (t (format t "x and y are equal")). In Elisp, t is the symbol for "true." Since t is always true, this clause acts as a catch-all or default case, similar to an else at the end of a chain of if/else if. It guarantees that if no other condition is met, this final block of code will run.


2. Seeing cond in Action

Let's watch a video that demonstrates cond with a variable, so you can see how changing the input affects which branch is chosen.

Types, Conditionals, and Loops - Learning Emacs Lisp #2

The System Crafters channel provides a very clear, step-by-step walkthrough of cond. This will help solidify the syntax and the concept of the default t clause.

Watch the segment from 01:06:20 to 01:08:13. Pay attention to how the cond expression evaluates different clauses as the value of the variable a is changed.

Now, let's replicate this in your *scratch* buffer to get a feel for it.

  1. Define a variable a.
    (setq a 2)
    
  2. Write the cond expression.
    (cond ((= a 1) "a is one.")
          ((> a 1) "a is greater than one.")
          (t "a is something else."))
    
  3. Place your cursor after the cond expression and press C-x C-e. You should see "a is greater than one." in the echo area.
  4. Now, go back and change the value of a to 1 and re-evaluate the (setq ...) expression. Then, re-evaluate the cond expression. You'll get "a is one.".
  5. Finally, change a to -5 and repeat. You'll see the default case, "a is something else.", is triggered.

This simple exercise demonstrates the core flow of cond: test, succeed, and stop; or test, fail, and continue.


3. A More Practical Example: Identifying Data Types

The previous example is good for demonstration, but let's look at something more functional. cond is perfect for building functions that inspect an object and behave differently based on its type.

Emacs-Lisp Conditionals | Logical Operators, Tests, Ifs, Conds | Switching to Emacs #6.2

This video from the Augusto channel shows a great practical use of cond to build a function that identifies the type of any Lisp object you give it.

Watch from 13:34 to 16:10. The presenter builds a type-of function using cond. Notice how each clause uses a predicate function (like numberp, stringp) as its test.

This type-of function is a fantastic example because it's something you might genuinely use while debugging or exploring Elisp. It highlights how cond can be used to check a series of properties about an object. The order of the clauses matters. As the video notes, if the symbolp test came before the functionp test, a function name like message would be identified as a symbol, not a function, because function names are also symbols.


4. Your Turn: Build a "Mode Describer" Command

Now let's apply this to your goal of building practical Emacs extensions. We will create a new interactive command that tells you something about the current buffer's major mode.

Your *scratch* buffer is likely in lisp-interaction-mode, but if you open a .el file, it will be in emacs-lisp-mode. If you open a .md file, it will be in markdown-mode. Our command will use cond to check the current major-mode and provide a friendly message.

  1. Start with the function definition:

    (defun describe-current-mode ()
      "Display a message describing the current major mode."
      (interactive))
    
  2. Add the cond block. The variable major-mode always holds the symbol for the current buffer's major mode. We'll use eq to compare it to different mode symbols.

    (defun describe-current-mode ()
      "Display a message describing the current major mode."
      (interactive)
      (cond
       ;; Add clauses here
       ))
    
  3. Add the clauses. Let's add a few for common modes. Notice that each body just calls the message function.

    (defun describe-current-mode ()
      "Display a message describing the current major mode."
      (interactive)
      (cond ((eq major-mode 'emacs-lisp-mode)
             (message "You are editing some powerful Emacs Lisp!"))
            
            ((eq major-mode 'lisp-interaction-mode)
             (message "This is the *scratch* buffer, great for testing Elisp."))
    
            ((eq major-mode 'markdown-mode)
             (message "Writing in Markdown, are we?"))
            
            ;; The default case
            (t
             (message "This buffer is in %s." major-mode))))
    

    The final (t ...) clause is our robust fallback. It will handle any mode we didn't explicitly list, using message's formatting capability (%s) to insert the name of the mode into the string.

  4. Evaluate and test. Press C-x C-e after the final parenthesis to define the function.

    • Now, run M-x describe-current-mode in your *scratch* buffer. You should see the message for lisp-interaction-mode.
    • Open a new file called test.md. Emacs will put it in markdown-mode. Run M-x describe-current-mode there. You'll get the Markdown message.
    • Open a .txt file. It will likely open in text-mode. Run the command, and you'll see the default case in action: This buffer is in text-mode.

You've successfully created an intelligent command that adapts its output based on a series of conditions, all neatly organized with cond.


Conclusion

You have now moved beyond simple binary decisions and can write code that handles a multitude of scenarios gracefully. cond is a cornerstone of Lisp programming, valued for its clarity and power in expressing complex conditional logic.

Key Takeaways:

  • cond is Elisp's primary tool for multi-branch conditional logic, evaluating a series of clauses in order.
  • The structure is (cond (TEST-1 BODY-1...) (TEST-2 BODY-2...) ...).
  • It executes the body of the first clause whose test evaluates to a non-nil value and then stops.
  • A clause can have multiple forms in its body, which are executed sequentially (an "implicit progn").
  • The pattern (t ...) is used to create a default or else case that runs if no other conditions are met.
  • For logic involving more than two distinct paths, cond is almost always cleaner and more readable than nested if statements.

Preview of the Next Lesson

So far, our tests have been simple, single expressions like (eobp) or (= a 1). But what if you need to check if two, or three, or more things are true at the same time? In the next lesson, we will explore the logical operators and, or, and not, which allow you to combine and invert conditions to build even more precise and powerful tests for your if and cond expressions.