Skip to main content

The Craft of Emacs

Pairs: car and cdr

1 hour

Make sure that the code you have is the same as the previous source listing:

(defunxx
()
(interactive)
(get‑buffer‑create"*xx*")
(switch‑to‑buffer"*xx*")
(erase‑buffer)
(xx‑‑insert‑line"Skeins")
(xx‑‑insert‑line"======")
(let*
(
(strands
(xx‑‑read‑positive"No. strands: ")
)
(colours
(xx‑‑read‑colours)
)
(stitches
(seq‑map'xx‑‑read‑stitchescolours)
)
(num‑skeins‑function
(lambda
(count)
(xx‑‑num‑skeinsstrandscount)
)
)
(num‑skeins
(seq‑mapnum‑skeins‑functionstitches)
)
(total‑num‑skeins
(apply'+num‑skeins)
)
)
(xx‑‑insert‑line
(concat"No. strands: "
(number‑to‑stringstrands)
)
)
(xx‑‑insert‑line
(concat"Total no. skeins: "
(number‑to‑stringtotal‑num‑skeins)
)
)
(seq‑mapn
(lambda
(colournum)
(xx‑‑insert‑line
(concat"No. "
(symbol‑namecolour)
" skeins : "
(number‑to‑stringnum)
)
)
)
coloursnum‑skeins)
)
)
(defunxx‑‑read‑stitches
(colour)
(let
(
(name
(symbol‑namecolour)
)
)
(xx‑‑read‑positive
(concat"How many stitches are "name"? ")
)
)
)
(defunxx‑‑insert‑line
(text)
(inserttext)
(newline)
)
(defunxx‑‑num‑skeins
(strandsstitches)
(ceiling
(/
(float
(*strandsstitches)
)
4800.0)
)
)
(defunxx‑‑read‑positive
(prompt)
(let
(
(input
(read‑numberprompt)
)
)
(if
(and
(integerpinput)
(>input0)
)
input
(message"Enter an integer greater than 0.")
(sit‑for1)
(xx‑‑read‑positiveprompt)
)
)
)
xx.el

With that, you’re good to go.

The updated xx function asks us to input each colour. Instead of having a fixed list of colours, it builds one on the fly based on what we input.

To code this, we need to understand how lists are structured. And for that, we need to look at the cons cell.

A cons cell is a pair of elements. It’s named after cons, the function used to construct it.

Evaluate this cons cell.

(cons"apple""pie")
*scratch*

Emacs prints a cons cell as a two parenthesized elements with a dot (.) in between.

You can think of a cons cell as having two slots, each of which refers to an s-expression. The order of the slots is important — (cons "lemon" "tart") isn’t the same as (cons "tart" "lemon").

How would you test this?

The slots of a cons cell can refer to different types of data. They can even refer to other cons cells.

Evaluate this cons cell.

(cons
(cons"rhubarb""pear")
'pudding)
*scratch*

Nested cons cells can quickly become confusing. We’ll visualize them using trees.

Your Emacs includes a handy tool to visualize them: M‑x pair‑tree.

Use M‑x pair‑tree to visualize the cons cell.

M-x ^pair-tree (cons (cons "rhubarb" "pear") 'pudding) 

M‑x pair‑tree doesn’t come bundled in Emacs by default — you installed it when setting up.

Calling it from the minibuffer can be a bit tiresome. Like any other function, you can also call it from the *scratch* buffer.

(pair‑tree
(cons
(cons"rhubarb""pear")
'pudding)
)
*scratch*

We can find out what each slot refers to using the obscurely named car and cdr functions.

The first slot in a cons cell is known as the car. Its value is obtained using the car function.

Use car to inspect the first slot.

(car
(cons"peach""cobbler")
)
*scratch*

The second slot is known as the cdr, pronounced “could‑er”. As with the car, it has a respective cdr function.

Use cdr to inspect the second slot.

(cdr
(cons"peach""cobbler")
)
*scratch*

The names car and cdr are a bit cryptic, but date far back to Emacs’s birth.

As cons cells can slot other cons cells, we can use them to build larger data structures. In fact, they are used to build the most important data structure in Elisp.

A list is a series of linked cons cells.

This is a difficult point to grasp, and I’m sure you’re still a little confused.

To demonstrate, let’s walk down a list and rewrite it using the cons constructor.

'
("sticky""toffee""pudding")

The car is "sticky" and the cdr is ("toffee" "pudding")

(cons"sticky"'
("toffee""pudding")
)

The car is "toffee" and the cdr is ("pudding")

(cons"sticky"
(cons"toffee"'
("pudding")
)
)

The car is "pudding" and the cdr is nil

(cons"sticky"
(cons"toffee"
(cons"pudding"nil)
)
)

This nested cons cells structure is a little complicated.

Explore it using M‑x pair‑tree.

(pair‑tree
(cons"sticky"
(cons"toffee"
(cons"pudding"nil)
)
)
)
*scratch*

Does it really result in the same structure as the quoted list?

Explore the quoted list using M‑x pair‑tree.

(pair‑tree'
("sticky""toffee""pudding")
)
*scratch*

We can be absolutely sure that the lists are equal by evaluating the expressions.

Verify that a list constructed using cons is equal to one resulting from quote.

(equal'
("sticky""toffee""pudding")
(cons"sticky"
(cons"toffee"
(cons"pudding"nil)
)
)
)
*scratch*

Cons cells are so ubiquitous in Elisp that there’s an additional method of constructing them — the dot (.). Constructing cons cells like this is known as using dotted pair notation.

Evaluate the dotted pair.

'
("vanilla"."cheesecake")
*scratch*

The interpreter reads a dot within a quoted expression as a cons cell.

Unlike an apostrophe, this isn’t a shorthand — it’s never rewritten to a call to the cons function. Instead of rewriting it, Elisp constructs the cons cell in the read stage. The call to cons isn’t part of evaluation.

If we wished, we could use dotted pair notation to write lists.

What list does this evaluate to?

'
("cinnamon".
("bun".nil)
)
*scratch*

Don’t write lists like this in your own code — it’s far more obscure than '("cinnamon" "bun").

Use M‑x pair‑tree to explore the pairs above.

We can use the cons function to dynamically build a list of colours.

Alter xx to use a new xx‑‑read‑colours function.

(defunxx
()
(interactive)
(get‑buffer‑create"*xx*")
(switch‑to‑buffer"*xx*")
(erase‑buffer)
(xx‑‑insert‑line"Skeins")
(xx‑‑insert‑line"======")
(let*
(
(strands
(xx‑‑read‑positive"No. strands: ")
)
(colours
(xx‑‑read‑colours)
)
(stitches
(seq‑map'xx‑‑read‑stitchescolours)
)
(num‑skeins‑function
(lambda
(count)
(xx‑‑num‑skeinsstrandscount)
)
)
(num‑skeins
(seq‑mapnum‑skeins‑functionstitches)
)
(total‑num‑skeins
(apply'+num‑skeins)
)
)
(xx‑‑insert‑line
(concat"No. strands: "
(number‑to‑stringstrands)
)
)
(xx‑‑insert‑line
(concat"Total no. skeins: "
(number‑to‑stringtotal‑num‑skeins)
)
)
(seq‑mapn
(lambda
(colournum)
(xx‑‑insert‑line
(concat"No. "
(symbol‑namecolour)
" skeins : "
(number‑to‑stringnum)
)
)
)
coloursnum‑skeins)
)
)
(defunxx‑‑read‑stitches
(colour)
(let
(
(name
(symbol‑namecolour)
)
)
(xx‑‑read‑positive
(concat"How many stitches are "name"? ")
)
)
)
(defunxx‑‑insert‑line
(text)
(inserttext)
(newline)
)
(defunxx‑‑num‑skeins
(strandsstitches)
(ceiling
(/
(float
(*strandsstitches)
)
4800.0)
)
)
(defunxx‑‑read‑positive
(prompt)
(let
(
(input
(read‑numberprompt)
)
)
(if
(and
(integerpinput)
(>input0)
)
input
(message"Enter an integer greater than 0.")
(sit‑for1)
(xx‑‑read‑positiveprompt)
)
)
)
(defunxx‑‑read‑colours
()
)
xx.el

xx‑‑read‑colours must ask the user to input colours one by one.

It places the inputted colour as the first element in a list. In other words, it creates a cons cell with a car referring to the colour.

Create a cons cell with an inputted colour.

(defunxx
()
(interactive)
(get‑buffer‑create"*xx*")
(switch‑to‑buffer"*xx*")
(erase‑buffer)
(xx‑‑insert‑line"Skeins")
(xx‑‑insert‑line"======")
(let*
(
(strands
(xx‑‑read‑positive"No. strands: ")
)
(colours
(xx‑‑read‑colours)
)
(stitches
(seq‑map'xx‑‑read‑stitchescolours)
)
(num‑skeins‑function
(lambda
(count)
(xx‑‑num‑skeinsstrandscount)
)
)
(num‑skeins
(seq‑mapnum‑skeins‑functionstitches)
)
(total‑num‑skeins
(apply'+num‑skeins)
)
)
(xx‑‑insert‑line
(concat"No. strands: "
(number‑to‑stringstrands)
)
)
(xx‑‑insert‑line
(concat"Total no. skeins: "
(number‑to‑stringtotal‑num‑skeins)
)
)
(seq‑mapn
(lambda
(colournum)
(xx‑‑insert‑line
(concat"No. "
(symbol‑namecolour)
" skeins : "
(number‑to‑stringnum)
)
)
)
coloursnum‑skeins)
)
)
(defunxx‑‑read‑stitches
(colour)
(let
(
(name
(symbol‑namecolour)
)
)
(xx‑‑read‑positive
(concat"How many stitches are "name"? ")
)
)
)
(defunxx‑‑insert‑line
(text)
(inserttext)
(newline)
)
(defunxx‑‑num‑skeins
(strandsstitches)
(ceiling
(/
(float
(*strandsstitches)
)
4800.0)
)
)
(defunxx‑‑read‑positive
(prompt)
(let
(
(input
(read‑numberprompt)
)
)
(if
(and
(integerpinput)
(>input0)
)
input
(message"Enter an integer greater than 0.")
(sit‑for1)
(xx‑‑read‑positiveprompt)
)
)
)
(defunxx‑‑read‑colours
()
(let
(
(colour
(read‑string"What colour is the thread? ")
)
)
(conscolour)
)
)
xx.el

The cdr of the cons cell must hold any remaining types. We can get these by calling xx‑‑read‑colours again.

Call xx‑‑read‑colours within its own body.

(defunxx
()
(interactive)
(get‑buffer‑create"*xx*")
(switch‑to‑buffer"*xx*")
(erase‑buffer)
(xx‑‑insert‑line"Skeins")
(xx‑‑insert‑line"======")
(let*
(
(strands
(xx‑‑read‑positive"No. strands: ")
)
(colours
(xx‑‑read‑colours)
)
(stitches
(seq‑map'xx‑‑read‑stitchescolours)
)
(num‑skeins‑function
(lambda
(count)
(xx‑‑num‑skeinsstrandscount)
)
)
(num‑skeins
(seq‑mapnum‑skeins‑functionstitches)
)
(total‑num‑skeins
(apply'+num‑skeins)
)
)
(xx‑‑insert‑line
(concat"No. strands: "
(number‑to‑stringstrands)
)
)
(xx‑‑insert‑line
(concat"Total no. skeins: "
(number‑to‑stringtotal‑num‑skeins)
)
)
(seq‑mapn
(lambda
(colournum)
(xx‑‑insert‑line
(concat"No. "
(symbol‑namecolour)
" skeins : "
(number‑to‑stringnum)
)
)
)
coloursnum‑skeins)
)
)
(defunxx‑‑read‑stitches
(colour)
(let
(
(name
(symbol‑namecolour)
)
)
(xx‑‑read‑positive
(concat"How many stitches are "name"? ")
)
)
)
(defunxx‑‑insert‑line
(text)
(inserttext)
(newline)
)
(defunxx‑‑num‑skeins
(strandsstitches)
(ceiling
(/
(float
(*strandsstitches)
)
4800.0)
)
)
(defunxx‑‑read‑positive
(prompt)
(let
(
(input
(read‑numberprompt)
)
)
(if
(and
(integerpinput)
(>input0)
)
input
(message"Enter an integer greater than 0.")
(sit‑for1)
(xx‑‑read‑positiveprompt)
)
)
)
(defunxx‑‑read‑colours
()
(let
(
(colour
(read‑string"What colour is the thread? ")
)
)
(conscolour
(xx‑‑read‑colours)
)
)
)
xx.el

Test this by calling M‑x xx.

What colour is the thread? black
What colour is the thread? magenta

We’re indeed asked to enter each colour. But there’s a problem: we can’t stop.

The function asks us for colours indefinitely, with no way for the us to say that we’ve finished. We need some way of indicating that all colours have been entered.

We can do this by leaving the input blank. If the input is an empty string, there can be no more colours to enter. The function results in nil

We’ll need a predicate function to test whether a string is empty. Perhaps Emacs has one? We can browse a summary of string related functions using the Function Group Overview, just as we did in the previous chapter for sequence.

Look at the Function Group Overview for string. Find a predicate that tests for a blank string.

Predicates for Strings

...

(string-blank-p string)
  Check whether STRING is either empty or only whitespace.
  (string-blank-p " \n")
    ⇒ 0
*Shortdoc string*

string‑blank‑p looks promising.

Give it a try in the *scratch* buffer.

(string‑blank‑p"eclair")
(string‑blank‑p"  ")
*scratch*

Use string‑blank‑p to test for an empty string.

(defunxx
()
(interactive)
(get‑buffer‑create"*xx*")
(switch‑to‑buffer"*xx*")
(erase‑buffer)
(xx‑‑insert‑line"Skeins")
(xx‑‑insert‑line"======")
(let*
(
(strands
(xx‑‑read‑positive"No. strands: ")
)
(colours
(xx‑‑read‑colours)
)
(stitches
(seq‑map'xx‑‑read‑stitchescolours)
)
(num‑skeins‑function
(lambda
(count)
(xx‑‑num‑skeinsstrandscount)
)
)
(num‑skeins
(seq‑mapnum‑skeins‑functionstitches)
)
(total‑num‑skeins
(apply'+num‑skeins)
)
)
(xx‑‑insert‑line
(concat"No. strands: "
(number‑to‑stringstrands)
)
)
(xx‑‑insert‑line
(concat"Total no. skeins: "
(number‑to‑stringtotal‑num‑skeins)
)
)
(seq‑mapn
(lambda
(colournum)
(xx‑‑insert‑line
(concat"No. "
(symbol‑namecolour)
" skeins : "
(number‑to‑stringnum)
)
)
)
coloursnum‑skeins)
)
)
(defunxx‑‑read‑stitches
(colour)
(let
(
(name
(symbol‑namecolour)
)
)
(xx‑‑read‑positive
(concat"How many stitches are "name"? ")
)
)
)
(defunxx‑‑insert‑line
(text)
(inserttext)
(newline)
)
(defunxx‑‑num‑skeins
(strandsstitches)
(ceiling
(/
(float
(*strandsstitches)
)
4800.0)
)
)
(defunxx‑‑read‑positive
(prompt)
(let
(
(input
(read‑numberprompt)
)
)
(if
(and
(integerpinput)
(>input0)
)
input
(message"Enter an integer greater than 0.")
(sit‑for1)
(xx‑‑read‑positiveprompt)
)
)
)
(defunxx‑‑read‑colours
()
(let
(
(colour
(read‑string"What colour is the thread? ")
)
)
(if
(string‑blank‑pcolour)
nil
(conscolour
(xx‑‑read‑colours)
)
)
)
)
xx.el

Test this by calling M‑x xx.

We can now finish entering colours, but we fail later on in the skein calculation.

Wrong type argument: symbolp, "black"

By now you’re probably somewhat used to Emacs’s cryptic errors. Don’t panic: let’s take a step back and examine the message.

Something somewhere is the wrong type. One of our functions expected a symbol, tested by symbolp , but was passed the string "black" instead.

This is expected — the value of each colour has changed from the hard-coded symbol black to the inputted string "black". Our previous functions on symbols should break.

Remove the calls to symbol‑name.

(defunxx
()
(interactive)
(get‑buffer‑create"*xx*")
(switch‑to‑buffer"*xx*")
(erase‑buffer)
(xx‑‑insert‑line"Skeins")
(xx‑‑insert‑line"======")
(let*
(
(strands
(xx‑‑read‑positive"No. strands: ")
)
(colours
(xx‑‑read‑colours)
)
(stitches
(seq‑map'xx‑‑read‑stitchescolours)
)
(num‑skeins‑function
(lambda
(count)
(xx‑‑num‑skeinsstrandscount)
)
)
(num‑skeins
(seq‑mapnum‑skeins‑functionstitches)
)
(total‑num‑skeins
(apply'+num‑skeins)
)
)
(xx‑‑insert‑line
(concat"No. strands: "
(number‑to‑stringstrands)
)
)
(xx‑‑insert‑line
(concat"Total no. skeins: "
(number‑to‑stringtotal‑num‑skeins)
)
)
(seq‑mapn
(lambda
(colournum)
(xx‑‑insert‑line
(concat"No. "colour" skeins : "
(number‑to‑stringnum)
)
)
)
coloursnum‑skeins)
)
)
(defunxx‑‑read‑stitches
(colour)
(xx‑‑read‑positive
(concat"How many stitches are "colour"? ")
)
)
(defunxx‑‑insert‑line
(text)
(inserttext)
(newline)
)
(defunxx‑‑num‑skeins
(strandsstitches)
(ceiling
(/
(float
(*strandsstitches)
)
4800.0)
)
)
(defunxx‑‑read‑positive
(prompt)
(let
(
(input
(read‑numberprompt)
)
)
(if
(and
(integerpinput)
(>input0)
)
input
(message"Enter an integer greater than 0.")
(sit‑for1)
(xx‑‑read‑positiveprompt)
)
)
)
(defunxx‑‑read‑colours
()
(let
(
(colour
(read‑string"What colour is the thread? ")
)
)
(if
(string‑blank‑pcolour)
nil
(conscolour
(xx‑‑read‑colours)
)
)
)
)
xx.el

Our new M‑x xx function now lets us enter whichever colours we like.

Call M‑x xx with:

  • 4 strands

  • A colour of black

  • A colour of magenta

  • A colour of pink

  • 1500 black stitches

  • 1450 magenta stitches

  • 2500 pink stitches

If all is well this will give:

Skeins
======
No. strands: 4
Total no. skeins: 7
No. black skeins : 2
No. magenta skeins : 2
No. pink skeins : 3
*xx*