Skip to main content

Welcome to a Grasp lesson

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

Lesson 46

Mastering Property Lists: Get & Put

Recommended resources

Introduction

Welcome to the next lesson in our journey through Elisp's data structures. In the previous lesson, we dove deep into association lists (alists), learning how to add and update their entries using both non-destructive shadowing and destructive modification with setf and alist-get. You learned that alists are lists of (key . value) pairs.

Today, we will explore the learning outcome: Create property lists (plists) and manage key-value pairs using 'plist-get' and 'plist-put'. Property lists, or "plists," are another fundamental key-value structure in Lisp, but with a different organization and distinct use cases. We will cover what they are, how to create them, and how to read from and write to them. This will round out your understanding of list-based key-value storage before we compare them in the next lesson.


1. What is a Property List (Plist)?

While an alist is a list of pairs, a property list (plist) is a simple, flat list where elements alternate between a key and its corresponding value.

  • Alist structure: ((key1 . value1) (key2 . value2) ...)
  • Plist structure: (key1 value1 key2 value2 ...)

By convention, the keys in a plist are typically keywords. A keyword is a special type of symbol that starts with a colon, such as :name or :status. Keywords are constants that evaluate to themselves, which makes them very convenient for use as identifiers—you don't need to quote them.

Let's watch a short video that introduces the basic structure of a plist and how to create one.

scimax - data structures in emacs-lisp

The following clip from a video by John Kitchin provides a concise introduction to property lists. He contrasts them with alists and shows how to create one using keywords.

Watch from 00:17:45 to 00:18:29. Pay attention to the syntax he uses to define the plist with keywords and the resulting flat list structure.

As you saw, creating a plist is straightforward. Let's create one based on your interest in creative writing. Open your *scratch* buffer and evaluate this expression:

(setq my-character
      '(:name "Riva"
        :origin "Cygnus X-1"
        :affiliation "Galactic Concordance"))

;; The value of my-character is now a plist:
;; => (:name "Riva" :origin "Cygnus X-1" :affiliation "Galactic Concordance")

Notice the structure is a single list, not a list of lists like an alist would be.


2. Retrieving Data with plist-get

To retrieve a value from a plist, you use the function plist-get. Its signature is (plist-get PLIST PROPERTY). It scans the PLIST for the PROPERTY key and returns the value that immediately follows it. If the property is not found, it returns nil.

For a definitive explanation, please consult the GNU Emacs Lisp Reference Manual.

Plist Access (GNU Emacs Lisp Reference Manual)

The official documentation for plist-get explains its purpose and provides clear examples.

Read the description for the plist-get function. Note its arguments and the examples provided, especially what happens when a property is not found.

Now, let's use plist-get with the character profile we created:

;; Get the character's name
(plist-get my-character :name)
;; => "Riva"

;; Get the character's affiliation
(plist-get my-character :affiliation)
;; => "Galactic Concordance"

;; Look for a property that doesn't exist
(plist-get my-character :skill)
;; => nil

Plists are frequently used by built-in Emacs functions to return structured data, such as information from web APIs or configuration sources. Let's see a practical example of plist-get in action.

Let's write some Emacs Lisp - Using URL and authinfo to get a list of PRs from Github

In this video, Geoffrey Lessel writes a function to fetch GitHub pull requests. The API response, once parsed, is a list of plists. This segment shows how he uses plist-get to extract the 'number' and 'title' from each pull request plist.

Watch from 01:01:33 to 01:08:16. You don't need to understand every detail of the code. Focus on how he defines a lambda function that takes a PR (a plist) and uses (plist-get pr :number) and (plist-get pr :title) to extract the data he needs.

This real-world example demonstrates a common pattern: iterating over a list of plists and using plist-get to process each one.


3. Adding and Updating Data with plist-put

To add a new key-value pair or update an existing one, you use plist-put. Its signature is (plist-put PLIST PROPERTY VALUE).

This function is interesting because of how it handles modification. Like some of the functions we saw in the last lesson, plist-put returns a new, updated plist. It might modify the original list structure in place for efficiency, or it might create a new one. Because of this ambiguity, the correct and safe way to use it is to always assign the result back to your variable, like this:

(setq my-plist (plist-put my-plist :property value))

Let's turn back to the manual for the official description.

Plist Access (GNU Emacs Lisp Reference Manual)

The manual's entry for plist-put clarifies how to use it for both adding and updating properties.

Read the description and examples for the plist-put function. Pay close attention to the sentence: "The function returns the modified property list, so you can store that back in the place where you got plist."

Let's apply this to our character profile.

;; First, let's update an existing property.
;; Riva has a new affiliation.
(setq my-character
      (plist-put my-character :affiliation "Free Worlds Alliance"))

my-character
;; => (:name "Riva" :origin "Cygnus X-1" :affiliation "Free Worlds Alliance")

;; Now, let's add a new property that wasn't there before.
(setq my-character
      (plist-put my-character :status "Active"))

my-character
;; => (:name "Riva" :origin "Cygnus X-1" :affiliation "Free Worlds Alliance" :status "Active")

This (setq ... (plist-put ...)) pattern ensures that your variable always holds the correct, fully updated plist, regardless of whether the modification was destructive or not.


4. Practical Application: A Speculative Fiction Character Database

Let's build a small utility that leverages your interest in world-building and speculative fiction. We'll create a "database" (just a variable) holding character profiles as plists and write an interactive function to display them.

First, define the database. This will be an alist where each key is a character's name and the value is their profile plist.

(defvar speculative-fiction-db
  '(("Kaelen" . (:class "Psion" :origin "Xylos" :ship "Stargazer"))
    ("Jax" . (:class "Engineer" :origin "Earth" :ship "The Venture"))
    ("Nyx" . (:class "Infiltrator" :origin "Proxima b" :ship "Shadow")))
  "A database of characters for a speculative fiction project.
It is an alist where keys are names and values are plists of character data.")

Copy this into your *scratch* buffer and evaluate it with C-j.

Now, let's write an interactive command to look up a character and display their profile.

(defun show-character-profile (name)
  "Look up a character by NAME and display their profile."
  (interactive "sEnter character name: ")
  ;; `assoc` retrieves the (name . plist) pair from our alist.
  (let* ((entry (assoc name speculative-fiction-db))
         (profile-plist (cdr entry)))
    (if profile-plist
        ;; If we found a profile, use plist-get to format a message.
        (message "Profile for %s:\n - Class: %s\n - Origin: %s\n - Ship: %s"
                 name
                 (plist-get profile-plist :class)
                 (plist-get profile-plist :origin)
                 (plist-get profile-plist :ship))
      ;; Otherwise, report that the character was not found.
      (message "Character '%s' not found in database." name))))

Evaluate this function definition. Now you can use it!

  1. Run M-x show-character-profile.
  2. Enter Jax at the prompt.
  3. You should see a formatted message in the echo area with Jax's profile. Try it with Kaelen as well.

This example combines what you've learned:

  • An alist (speculative-fiction-db) acts as our primary index.
  • assoc finds the right character entry.
  • Plists hold the structured data for each character.
  • plist-get allows us to easily extract the specific attributes we need for our message.

Conclusion

In this lesson, we demystified property lists, a key-value data structure that complements the alists we learned about previously. You now have another essential tool for managing structured data in your Elisp code.

Key Takeaways:

  • Plist Structure: A plist is a flat list of alternating keys and values: (:key1 value1 :key2 value2 ...).
  • Keywords: Plists conventionally use keywords (e.g., :name) as keys because they are efficient and self-evaluating.
  • Retrieval: The plist-get function is the standard way to retrieve a value for a given key. It returns nil if the key is not found.
  • Modification: The plist-put function is used to add or update a key-value pair. The standard, safe idiom is to assign the result back to your variable: (setq my-plist (plist-put my-plist ...)).

Preview of the Next Lesson

You are now proficient with two different list-based key-value structures: alists and plists. This naturally leads to the question: which one should you use, and when? In our next lesson, we will directly address this by comparing alists and plists in terms of their performance characteristics and common use cases. We will also introduce a third, more powerful structure—the hash table—to complete your toolkit for handling key-value data in Emacs Lisp.