Set up
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)(lambdacoloursnum‑skeins)(colournum)(xx‑‑insert‑line)(concat"No. ")(symbol‑namecolour)" skeins : "(number‑to‑stringnum))(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)(/)(float4800.0)(*strandsstitches))(defunxx‑‑read‑positive(prompt)(let)((input)(read‑numberprompt))(if)(andinput(integerpinput)(>input0))(message"Enter an integer greater than 0.")(sit‑for1)(xx‑‑read‑positiveprompt))
With that, you’re good to go.
Building lists on the fly
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.
Introducing 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")
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)
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)
Inspecting the slots
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"))
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"))
The names car and cdr are a bit cryptic, but date far back to Emacs’s birth.
The structure of lists
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.
A non-empty list is itself a cons cell.
Its car (the first slot) refers to the first element in the list. Its cdr (the second slot) refers to the rest of the list.
An empty list is the symbol
nil
.
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))
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"))
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))
Dotted pair notation
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")
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))
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.
Reading in the colours
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)(lambdacoloursnum‑skeins)(colournum)(xx‑‑insert‑line)(concat"No. ")(symbol‑namecolour)" skeins : "(number‑to‑stringnum))(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)(/)(float4800.0)(*strandsstitches))(defunxx‑‑read‑positive(prompt)(let)((input)(read‑numberprompt))(if)(andinput(integerpinput)(>input0))(message"Enter an integer greater than 0.")(sit‑for1)(xx‑‑read‑positiveprompt))(defunxx‑‑read‑colours())
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)(lambdacoloursnum‑skeins)(colournum)(xx‑‑insert‑line)(concat"No. ")(symbol‑namecolour)" skeins : "(number‑to‑stringnum))(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)(/)(float4800.0)(*strandsstitches))(defunxx‑‑read‑positive(prompt)(let)((input)(read‑numberprompt))(if)(andinput(integerpinput)(>input0))(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))
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)(lambdacoloursnum‑skeins)(colournum)(xx‑‑insert‑line)(concat"No. ")(symbol‑namecolour)" skeins : "(number‑to‑stringnum))(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)(/)(float4800.0)(*strandsstitches))(defunxx‑‑read‑positive(prompt)(let)((input)(read‑numberprompt))(if)(andinput(integerpinput)(>input0))(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))
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.
Terminating with blank inputs
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
string‑blank‑p
looks promising.
Give it a try in the *scratch*
buffer.
(string‑blank‑p"eclair")(string‑blank‑p" ")
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)(lambdacoloursnum‑skeins)(colournum)(xx‑‑insert‑line)(concat"No. ")(symbol‑namecolour)" skeins : "(number‑to‑stringnum))(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)(/)(float4800.0)(*strandsstitches))(defunxx‑‑read‑positive(prompt)(let)((input)(read‑numberprompt))(if)(andinput(integerpinput)(>input0))(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))
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"
Troubleshooting type errors
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)(lambdacoloursnum‑skeins)(colournum)(xx‑‑insert‑line)(concat"No. "colour" skeins : ")(number‑to‑stringnum))(defunxx‑‑read‑stitches(colour)(xx‑‑read‑positive)(concat"How many stitches are "colour"? "))(defunxx‑‑insert‑line(text)(inserttext)(newline))(defunxx‑‑num‑skeins(strandsstitches)(ceiling)(/)(float4800.0)(*strandsstitches))(defunxx‑‑read‑positive(prompt)(let)((input)(read‑numberprompt))(if)(andinput(integerpinput)(>input0))(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))
Our new M‑x xx
function now lets us enter whichever colours we like.
Call M‑x xx
with:
4
strandsA colour of black
A colour of magenta
A colour of pink
1500
black stitches1450
magenta stitches2500
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