Skip to main content

Welcome to a Grasp lesson

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

Lesson 4

Control Flow and Conditional Logic

Recommended resources

Introduction

In our last lesson, we learned how to define functions using defun, encapsulating a process with inputs and outputs. However, the functions we created were linear; they followed a single path of execution every time. To build truly useful and intelligent tools for your workflow, your code needs to make decisions.

This lesson is about control flow—the order in which the computer executes statements. We'll focus on conditional logic, which allows a program to choose different paths based on whether a condition is true or false. This is the foundation of decision-making in programming. Your learning outcome for this lesson is to: Explain control flow and the role of conditional logic in decision-making.

Every time you see Emacs behave differently in one mode versus another, or a command that works on a selected region but does something else if there isn't one, you're seeing conditional logic in action. Today, you'll learn the building blocks to create that logic yourself.


1. The Lisp Definition of Truth

Before we can make decisions, we must understand what Emacs Lisp considers "true" and "false". In many programming languages, you have specific boolean values like True and False. Lisp simplifies this radically.

  • False: The symbol nil (which is also equivalent to an empty list ()) is the one and only value that represents falsehood.
  • True: Any value that is not nil is considered true.

This concept is often called "truthiness". The number 0, an empty string "", and any other value that isn't nil will evaluate to true in a conditional context. This is a fundamental aspect of Lisp that is both simple and powerful.

Let's watch a short explanation of this concept and the logical operators that act upon it.

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

This clip from System Crafters clearly explains what Elisp considers true and false. It also introduces the basic logical operators not, and, and or.

Watch from 55:15 to 1:00:17. Pay close attention to the explanation of nil and the surprising fact that values like 0 are considered true.

For reinforcement, here is a textual explanation from the official GNU Emacs Lisp manual.

An Introduction to Programming in Emacs Lisp

The GNU manual provides a concise, authoritative definition of truth and falsehood in Elisp.

Navigate to Chapter 3 and read section 3.9, 'Truth and Falsehood in Emacs Lisp'. It's a short but crucial section.


2. The Basic Two-Way Decision: if

The most fundamental conditional construct in Elisp is if. It evaluates a condition and then executes one of two code paths. Its structure is:

(if CONDITION THEN-FORM ELSE-FORMS...)

  • CONDITION: An expression that is evaluated.
  • THEN-FORM: If the CONDITION evaluates to anything other than nil, this form is executed.
  • ELSE-FORMS...: If the CONDITION evaluates to nil, the subsequent forms are executed.

Like everything in Lisp, if is an expression that returns a value—specifically, the value of whichever form was executed. Let's see it in action.

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

This segment demonstrates how to use if for basic conditional branching and how to use progn to include multiple expressions in the 'then' branch.

Watch from 1:00:17 to 1:03:57. Follow along in your *scratch* buffer. Note how if itself returns a value.

Hands-On Practice

Let's modify the format-chapter-title function from our last lesson. We'll add logic to handle a "Prologue" specially.

  1. Start with our previous function definition:
    (defun format-chapter-title (number title-text)
      "Format a chapter title with its number."
      (concat "Chapter " (number-to-string number) ": " title-text))
    
  2. Now, let's add an if statement. If the number is 0, we'll return "Prologue: [title]". Otherwise, we'll use the original format.
    (defun format-chapter-title (number title-text)
      "Format a chapter title, treating chapter 0 as a Prologue."
      (if (= number 0)
          (concat "Prologue: " title-text)
        (concat "Chapter " (number-to-string number) ": " title-text)))
    
  3. Evaluate this defun block with C-x C-e to install the new version.
  4. Now, test both paths in your *scratch* buffer:
    (format-chapter-title 0 "The Before Times")
    ;; C-x C-e => "Prologue: The Before Times"
    
    (format-chapter-title 3 "A New Beginning")
    ;; C-x C-e => "Chapter 3: A New Beginning"
    

3. Simplified Conditionals: when and unless

Often, you only need to do something if a condition is true (and do nothing otherwise), or vice-versa. While you could use if with an empty else branch, Elisp provides more readable alternatives: when and unless.

  • (when CONDITION ...): Executes the body forms only if CONDITION is true.
  • (unless CONDITION ...): Executes the body forms only if CONDITION is false (nil).

Unlike if, the body of when and unless can contain multiple expressions without needing progn.

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

This short clip explains the when and unless macros, which offer a cleaner syntax for one-sided conditionals.

Watch from 1:03:57 to 1:06:20. Notice how these constructs make the code's intent clearer than a one-sided if statement.

For example, if we wanted a function to simply print a warning message, when is a perfect fit:

(when (> (length long-manuscript) 500)
  (message "Warning: Manuscript is getting very long!")
  (other-action-if-long))

4. Advanced Logic with and and or

The and and or operators in Lisp are more powerful than their counterparts in many other languages. They don't just return t or nil; they return the value of the expression that determined the outcome. This enables some very common and elegant Lisp idioms.

  • and: Evaluates expressions from left to right. It stops and returns nil as soon as it encounters a nil result. If all expressions are true, it returns the value of the last expression.
  • or: Evaluates expressions from left to right. It stops and returns the value of the first expression that is not nil. If all expressions are nil, it returns nil.

This behavior is called short-circuiting. Let's watch a detailed explanation.

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

This video by Augusto provides an excellent, in-depth look at how and and or work in Elisp, focusing on their unique evaluation and return value behavior.

Watch from 08:07 to 11:07. Focus on understanding what is returned by and and or in different scenarios. This is a key Lisp concept.

A common use of or is to provide a default value. For instance, to get a user's name or default to "Anonymous":
(or user-provided-name "Anonymous")
If user-provided-name is nil, the expression will evaluate to "Anonymous".


5. Multi-Way Branching: cond

What if you have more than two paths? You could nest if statements, but that quickly becomes unreadable. For handling multiple conditions, Elisp provides cond. It's analogous to an if/elif/else chain in Python or a switch statement.

The general structure is a series of clauses, where each clause contains a test and one or more actions:
(cond (TEST-1 ACTION-1...) (TEST-2 ACTION-2...) ...)

cond evaluates each TEST in order. For the first TEST that returns a non-nil value, it executes the corresponding ACTIONs and returns the value of the last action in that clause. It does not evaluate any subsequent clauses.

A common pattern is to make the final test t (which is always true) to create a default "else" case.

This flowchart illustrates the logic of a multi-branch conditional, which is exactly what cond implements.

If-Else If-Else Control Flow Flowchart
This flowchart shows how control flows through a conditional structure. Program execution follows a different path depending on whether Condition A is true, Condition B is true, or neither is true. This is the logical pattern implemented by `cond`.

Now, let's see how to write a cond expression in Elisp.

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

System Crafters demonstrates how to use cond for clean, multi-way branching, including how to create a catch-all 'else' case.

Watch from 1:06:20 to 1:08:23. This will show you how to refactor a messy set of conditions into a clean cond block.

Hands-On Practice

Let's upgrade format-chapter-title one last time using cond to handle a prologue, an epilogue, and regular chapters. Let's assume an epilogue is represented by the number -1.

(defun format-chapter-title (number title-text)
  "Format a chapter title using cond."
  (cond ((= number 0)
         (concat "Prologue: " title-text))
        ((= number -1)
         (concat "Epilogue: " title-text))
        ((> number 0)
         (concat "Chapter " (number-to-string number) ": " title-text))
        (t ; Default case for any other number
         (concat "Special Section: " title-text))))

Evaluate this new definition. Now test all the conditions:

(format-chapter-title 0 "Before")
(format-chapter-title 5 "The Journey")
(format-chapter-title -1 "After")
(format-chapter-title -10 "Appendix")

Conclusion

You now have the fundamental tools for making your Elisp code responsive and intelligent. You can guide the flow of execution down different paths based on runtime conditions.

Key Takeaways:

  • Control flow is the order of execution in a program; conditional logic alters this flow.
  • In Elisp, nil is the only "false" value. Everything else is "true".
  • if is for simple, two-way decisions.
  • when and unless provide cleaner syntax for one-way decisions.
  • and and or are powerful operators that short-circuit and return the value that determines the outcome.
  • cond is the preferred tool for handling multiple, mutually exclusive conditions in a readable way.

Preview of the Next Lesson:
Our decision-making is currently limited to single values like numbers. To write more complex and useful programs, we need to work with collections of data. In the next lesson, we will explore the purpose of data structures for organizing data, starting with the most important one in the Lisp world: the list.