side
0.25
S-expressions are, loosely speaking, pieces of lisp code. There are several different kinds.
A string is an s-expression for text, denoted by double quotes ""
.
Strings can be evaluated by placing the point after the last quote
and calling M‑x eval‑last‑sexp
.
Evaluate each of these strings.
"xx""(this‑is‑not‑a‑function)"""
A number is what you’d expect.
Check that these numbers evaluate to themselves.
421.10‑7
A group of parenthesized s-expressions is termed a list. We used them informally in the previous chapter.
Evaluate each list.
(length"Cookie")(+411)(tetris)
The interpreter evaluates a list as function call. The first s-expression is interpreted as the function name and the rest as its arguments.
The result of evaluation can be passed as an argument to other
functions by nesting the list within another. A nested list can be
evaluated independently by calling the command M‑x eval‑last‑sexp
with the
point just after it.
Evaluate both the outer and inner lists.
(*(+2010)3)
The attentive reader will notice another kind of s-expression
hidden among the rest: +
and tetris
are
also s-expressions, known as symbols. A symbol
is something that can be looked up by the interpreter.
One of the many uses of a symbol is as a function name. A valid function call is always a list with a symbol as its first s-expression.
What happens if you use something else?
((tetris))(123)
This time, the Invalid function
error explains itself.
We can now evaluate four kinds of s-expression:
Numbers are evaluated as themselves.
Strings are also evaluated as themselves.
Lists are evaluated as function calls.
Symbols are evaluated as function names when at the start of a list.
There’s an exception to the rule — defun
.
Recall the early definition of xx‑view
.
(defunxx‑view()(coe‑xx‑basket‑view))
Th defun
function has the arguments xx‑view
,
()
and (coe‑xx‑basket‑view)
. Unlike a normal function,
these arguments aren’t evaluated.
defun
is a special form. Special forms are
functions with their own evaluation mechanisms built into the
interpreter. Instead of evaluating its arguments, defun
uses them to load a function into the environment.
Calls to defun
follow a specific structure:
(defunNAME(ARG...)BODY...)
Elisp has many special forms, each with its own custom structure. The interpreter is incoherent when this structure isn’t followed and its error message, if any at all, will likely cause more confusion than clarity.
The special forms throughout this book are coloured green to distinguish them from usual functions. Don’t let their colour deceive you — they are difficult beasts, only tamed with practice.
As you may have gathered, defun
isn’t the only special
form. We’ll meet the let
and if
forms shortly.
These definitions are simple enough, but no doubt
s-expressions are still a little mysterious to you. Are they code,
data, or simply text?
For that matter, what are symbols? Why do we call
+
a symbol at one point and a function the next?
To understand these distinctions we must step back and find a new perspective.
Several perspectives, in fact.
Recall that there are two stages to the interpreter: reading followed by evaluation. The interpreter interprets “code” in a general sense, but the code in each of these stages is thought of differently.
The read stage produces an s-expression; a structure composed of lists, symbols, strings and numbers. One may view it as data without meaning. Nothing can be termed a function at this point because the interpreter doesn’t act on it.
Browse the structure of the s-expression below using your arrow keys.
Select an s-expression:
(*(+43)(length"Hello!"))
Subsequently, we have evaluation. The interpreter walks through the s-expression, looking up symbols and calling their respective functions.
Step through the evaluation of the s-expression below.
Evaluate the list …
(*(+43)(length"Hello!"))
*
is a function.
(*(+43)(length"Hello!"))
Evaluate the list …
(*(+43)(length"Hello!"))
+
is a function.
(*(+43)(length"Hello!"))
Evaluate4
to 4
(*(+43)(length"Hello!"))
Evaluate3
to 3
(*(+43)(length"Hello!"))
Evaluate the list to 7
(*7(length"Hello!"))
Evaluate the list …
(*7(length"Hello!"))
length
is a function.
(*7(length"Hello!"))
Evaluate"Hello!"
to "Hello!"
(*7(length"Hello!"))
Evaluate the list to 6
(*76)
Evaluate the list to 42
42
At this point, the symbols *
,
+
and length
have additional meaning — we
must consider them functions to describe evaluation. Similarly,
4
, 3
and "Hello!"
are
arguments.
It would be remiss not to mention one more perspective. Programming is a discourse between the computer and the human, and the more usual way of reading code is by following the programmer’s intent. The reading guides at the end of each chapter demonstrate this.
These distinctions then — code and data, symbols and functions — are made clear through the lens of the interpreter. At this point, they may seem pedantic and irrelevant to you. In simple languages, the programmer is only concerned with conveying intent, and they only delve into evaluation when they find bugs.
Elisp is different. We live far too close to the interpreter to comfortably ignore its workings. Its perspectives need to be explored.
Enough of studying the s-expressions. Let’s use them.
Open a new file with M‑x find‑file xx.el
and write a bare-bones xx
command.
Remember to evaluate it with M‑x eval‑defun
.
(defunxx()(interactive))
You can now call M‑x xx
, but it won’t do anything yet.
Create a *xx*
buffer with get‑buffer‑create
.
Pass it a "*xx*"
string for the buffer name.
(defunxx()(interactive)(get‑buffer‑create"*xx*"))
This seems not to do anything either. Did it really create a buffer?
Use M‑x list‑buffers
to check there is a
*xx*
buffer.
It would be useful to view it.
View *xx*
in a window using switch‑to‑buffer
.
(defunxx()(interactive)(get‑buffer‑create"*xx*")(switch‑to‑buffer"*xx*"))
How many *xx*
buffers are there?
Buffers are uniquely identified by their names, so
get‑buffer‑create
only creates a buffer if one does not
already exist.
Write text to the buffer with insert
.
(defunxx()(interactive)(get‑buffer‑create"*xx*")(switch‑to‑buffer"*xx*")(insert"Skeins"))
Call M‑x xx
.
Skeins
All is well so far.
Call M‑x xx
twice more.
SkeinsSkeinsSkeins
Much as we need skeins, this isn’t quite what we’re after.
Use erase‑buffer
to wipe away the old text.
(defunxx()(interactive)(get‑buffer‑create"*xx*")(switch‑to‑buffer"*xx*")(erase‑buffer)(insert"Skeins"))
Add some more text, inserting line breaks with newline
.
(defunxx()(interactive)(get‑buffer‑create"*xx*")(switch‑to‑buffer"*xx*")(erase‑buffer)(insert"Skeins")(newline)(insert"====")(newline)(insert"No. strands: ")(newline)(insert"No. stitches: ")(newline)(insert"No. skeins: ")(newline))
Call M‑x xx
.
Skeins ====== No. strands: No. stitches: No. skeins:
A true Elisper would weep.
We’ve duplicated insert
and newline
five times. If only
we had an function to do this instead.
(defunxx()(interactive)(get‑buffer‑create"*xx*")(switch‑to‑buffer"*xx*")(erase‑buffer)(xx‑‑insert‑line"Skeins")(xx‑‑insert‑line"====")(xx‑‑insert‑line"No. strands: ")(xx‑‑insert‑line"No. stitches: ")(xx‑‑insert‑line"No. skeins: "))
xx‑‑insert‑line
inserts some text and a new line. It
doesn’t exist, but we can write it.
Write a xx‑‑insert‑line
function that accepts a
single text
argument.
(defunxx()(interactive)(get‑buffer‑create"*xx*")(switch‑to‑buffer"*xx*")(erase‑buffer)(xx‑‑insert‑line"Skeins")(xx‑‑insert‑line"====")(xx‑‑insert‑line"No. strands: ")(xx‑‑insert‑line"No. stitches: ")(xx‑‑insert‑line"No. skeins: "))(defunxx‑‑insert‑line(text)(inserttext)(newline))
This function accepts one argument — the argument list contains a
single text
symbol. It doesn’t have to be named
text
; any name will do.
When we call the function, text
is evaluated to the
string we pass in.
We can get the number of stitches with the read‑string
function.
This writes a prompt to the minibuffer and returns the string
entered after it. Its result can be combined with other strings using
concat.
Use read‑string
to get the user input.
(defunxx()(interactive)(get‑buffer‑create"*xx*")(switch‑to‑buffer"*xx*")(erase‑buffer)(xx‑‑insert‑line"Skeins")(xx‑‑insert‑line"====")(xx‑‑insert‑line(concat"No. strands: ")(read‑string"No. strands: "))(xx‑‑insert‑line(concat"No. stitches: ")(read‑string"No. stitches: "))(xx‑‑insert‑line"No. skeins: "))(defunxx‑‑insert‑line(text)(inserttext)(newline))
We’ll now write a xx‑‑num‑skeins
function to
calculate the number of skeins to buy.
This takes the number of stitches and strands used as arguments.
Write a bare-bones xx‑‑num‑skeins
definition.
(defunxx()(interactive)(get‑buffer‑create"*xx*")(switch‑to‑buffer"*xx*")(erase‑buffer)(xx‑‑insert‑line"Skeins")(xx‑‑insert‑line"======")(xx‑‑insert‑line(concat"No. strands: ")(read‑string"No. strands: "))(xx‑‑insert‑line(concat"No. stitches: ")(read‑string"No. stitches: "))(xx‑‑insert‑line"No. skeins: "))(defunxx‑‑insert‑line(text)(inserttext)(newline))(defunxx‑‑num‑skeins(strandsstitches))
Elisp has several functions for working with numbers.
+
and ‑
correspond to addition and subtraction.
*
and /
to multiplication and division.
ceiling
and floor
to rounding up and down.
Let’s assume that each cross takes 1cm
of thread to make.
A skein contains six strands and is eight metres long, so contains
4800cm
of thread in total.
To calculate the number of skeins we need; we must
multiply the number of stitches by the number of strands, divide by
4800
and round up.
(defunxx()(interactive)(get‑buffer‑create"*xx*")(switch‑to‑buffer"*xx*")(erase‑buffer)(xx‑‑insert‑line"Skeins")(xx‑‑insert‑line"======")(xx‑‑insert‑line(concat"No. strands: ")(read‑string"No. strands: "))(xx‑‑insert‑line(concat"No. stitches: ")(read‑string"No. stitches: "))(xx‑‑insert‑line"No. skeins: "))(defunxx‑‑insert‑line(text)(inserttext)(newline))(defunxx‑‑num‑skeins(strandsstitches)(ceiling)(/)(*strandsstitches)4800)
Our pattern has six hundred stitches.
How many skeins does this give for six hundred stitches at four strands?
Evaluate (xx‑‑num‑skeins 4 600)
in the *scratch*
buffer.
Our cursory check shows that something’s amiss.
If you’ve programmed before, you might have an inkling of what’s wrong. Nevertheless, working through it is a good exercise in troubleshooting.
Write out the full expression in the *scratch*
buffer
and evaluate each list with M‑x eval‑last‑sexp
.
Which result is unexpected?
(ceiling(/)(*4600)4800)
Surprisingly, (/ (* 600 4) 4800)
gives 0
instead of 0.5
.
Elisp has two kinds of numbers: 600
and 4
are integers, however 0.5
is a decimal, termed a float.
When provided with integer arguments, /
returns their
integer quotient. (/ 2400 4800)
is the number of times
4800
fits into 2400
, which is 0
.
What is (/ 2400.0 4800.0)
?
We must pass /
floats to perform decimal division.
Convert the integers to floats using the float
function.
(defunxx()(interactive)(get‑buffer‑create"*xx*")(switch‑to‑buffer"*xx*")(erase‑buffer)(xx‑‑insert‑line"Skeins")(xx‑‑insert‑line"======")(xx‑‑insert‑line(concat"No. strands: ")(read‑string"No. strands: "))(xx‑‑insert‑line(concat"No. stitches: ")(read‑string"No. stitches: "))(xx‑‑insert‑line"No. skeins: "))(defunxx‑‑insert‑line(text)(inserttext)(newline))(defunxx‑‑num‑skeins(strandsstitches)(ceiling)(/)(float4800.0)(*strandsstitches))
Verify that (xx‑‑num‑skeins 4 600)
calculates a single skein.
Calling our function from xx
poses a problem.
Attempt to use xx‑‑num‑skeins
.
(defunxx()(interactive)(get‑buffer‑create"*xx*")(switch‑to‑buffer"*xx*")(erase‑buffer)(xx‑‑insert‑line"Skeins")(xx‑‑insert‑line"======")(xx‑‑insert‑line(concat"No. strands: ")(read‑string"No. strands: "))(xx‑‑insert‑line(concat"No. stitches: ")(read‑string"No. stitches: "))(xx‑‑insert‑line)(concat"No. skeins: ")(xx‑‑num‑skeins))(defunxx‑‑insert‑line(text)(inserttext)(newline))(defunxx‑‑num‑skeins(strandsstitches)(ceiling)(/)(float4800.0)(*strandsstitches))
We need to pass it the user inputs from read‑string
, but
also need to write these to the buffer. The inputs need to be
referred to in multiple places.
The let
special form facilitates this.
We’ve assumed that the thread length per stitch is
1cm
. This is an approximation based on the size of a
square in the canvas used.
A cross is formed by pulling the thread diagonally through the front of the canvas, then behind along the side, diagonally through the front again, and finally diagonally along the back.
The length of thread used to make a stitch can be expressed as:
(+diagonalsidediagonaldiagonal)
Suppose your canvas square has 0.25 cm
sides. You can
calculate the length using let
.
(let((side0.25)(diagonal)(*1.40.25))(+diagonalsidediagonaldiagonal))
Evaluate the let
call in the
*scratch*
buffer.
This code snippet is a bit complex. Let’s walk through it step by step.
Step through the evaluation of let
.
Evaluate the list …
(let((side0.25)(diagonal)(*1.40.25))(+diagonalsidediagonaldiagonal))
...
let
is a special form.
(let((side0.25)(diagonal)(*1.40.25))(+diagonalsidediagonaldiagonal))
...
Evaluate the bindings …
(let((side0.25)(diagonal)(*1.40.25))(+diagonalsidediagonaldiagonal))
...
Evaluate0.25
to 0.25
(let((side0.25)(diagonal)(*1.40.25))(+diagonalsidediagonaldiagonal))
...
Evaluate the list …
(let((side0.25)(diagonal)(*1.40.25))(+diagonalsidediagonaldiagonal))
...
*
is a function.
(let((side0.25)(diagonal)(*1.40.25))(+diagonalsidediagonaldiagonal))
...
Evaluate1.4
to 1.4
(let((side0.25)(diagonal)(*1.40.25))(+diagonalsidediagonaldiagonal))
...
Evaluate0.25
to 0.25
(let((side0.25)(diagonal)(*1.40.25))(+diagonalsidediagonaldiagonal))
...
Evaluate the list to 0.35
(let((side0.25)(diagonal0.35))(+diagonalsidediagonaldiagonal))
...
Bind side
to 0.25
(let((side0.25)(diagonal0.35))(+diagonalsidediagonaldiagonal))
side
0.25
...
Bind diagonal
to 0.35
(let((side0.25)(diagonal0.35))(+diagonalsidediagonaldiagonal))
side
0.25
diagonal
0.35
...
Evaluate the body …
(let((side0.25)(diagonal0.35))(+diagonalsidediagonaldiagonal))
side
0.25
diagonal
0.35
...
Evaluate the list …
(let((side0.25)(diagonal0.35))(+diagonalsidediagonaldiagonal))
side
0.25
diagonal
0.35
...
+
is a function.
(let((side0.25)(diagonal0.35))(+diagonalsidediagonaldiagonal))
side
0.25
diagonal
0.35
...
Evaluatediagonal
to 0.35
(let((side0.25)(diagonal0.35))(+0.35sidediagonaldiagonal))
side
0.25
diagonal
0.35
...
Evaluateside
to 0.35
(let((side0.25)(diagonal0.35))(+0.350.25diagonaldiagonal))
side
0.25
diagonal
0.35
...
Evaluatediagonal
to 0.35
(let((side0.25)(diagonal0.35))(+0.350.250.35diagonal))
side
0.25
diagonal
0.35
...
Evaluatediagonal
to 0.35
(let((side0.25)(diagonal0.35))(+0.350.250.350.35))
side
0.25
diagonal
0.35
...
Evaluate the list to 1.3
(let(1.3)(side0.25)(diagonal0.35))
side
0.25
diagonal
0.35
...
Evaluate the list to 1.3
1.3
...
The thread used for a stitch is 1.3
cm.
let
binds symbols to values and allows us to
refer to those symbols within its body.
(let(BODY...)(NAMEVALUE)...)
As a special form, let
has a custom evaluation strategy.
The interpreter evaluates the bindings by using its environment to store their values. Values stored within the environment are termed variables. The bound symbol is used as the variable name, and the result of the bound s-expression is stored as its value.
When the body is evaluated, the interpreter searches its environment for any symbols and substitutes their values.
side
0.25
diagonal
0.35
...
We know how let
works. It’s time to try and
break it.
Attempt to use the symbol rhino
that isn’t bound to a value.
(let((x6))(+xrhino))
The error Symbol’s value as variable is void: rhino
is reasonable, if oddly worded.
We can think of more creative experiments. Can a variable have the same name as a function?
Bind +
to the value 6
.
(let((+6))(+++))
This is valid code, if highly obscure.
Function and variable names are stored separately in the
environment. The interpreter looks up the function or variable
depending on the symbol’s position in a list. The first
+
in (+ + +)
refers to the +
function, while the last two refer to 6
.
+
instructions...
6
...
Do the bindings have to be independent?
Evaluate a let
form with one binding
calculated from another.
(let((side0.25)(diagonal)(*1.4side))(+diagonalsidediagonaldiagonal))
The interpreter complains that side
doesn’t have a variable: the
binding for diagonal
cannot depend on it. This is
the most obvious limitation of the let
form. We’ll
overcome it in the next chapter. For now, independent bindings suit
our needs.
Use let
to refer to the user inputs.
(defunxx()(interactive)(get‑buffer‑create"*xx*")(switch‑to‑buffer"*xx*")(erase‑buffer)(xx‑‑insert‑line"Skeins")(xx‑‑insert‑line"======")(let)((strands(read‑string"No. strands: "))(stitches)(read‑string"No. stitches: "))(xx‑‑insert‑line(concat"No. strands: "strands))(xx‑‑insert‑line(concat"No. stitches: "stitches))(xx‑‑insert‑line)(concat"No. skeins: ")(xx‑‑num‑skeinsstrandsstitches))(defunxx‑‑insert‑line(text)(inserttext)(newline))(defunxx‑‑num‑skeins(strandsstitches)(ceiling)(/)(float4800.0)(*strandsstitches))
Call M‑x xx
with 600
stitches and
4
strands.
The interpreter prints a rather obscure error.
Wrong type argument: number-or-marker-p, "600"
Troubleshoot by writing the xx‑‑num‑skeins
body
in the *scratch*
buffer. Substitute the arguments "600"
and "4"
.
The culprit is the *
function. It reasonably expects a
number but we’ve given it the string "600"
.
Use the functions string‑to‑number
and
number‑to‑string
to convert the arguments and result of
xx‑‑num‑skeins
.
(defunxx()(interactive)(get‑buffer‑create"*xx*")(switch‑to‑buffer"*xx*")(erase‑buffer)(xx‑‑insert‑line"Skeins")(xx‑‑insert‑line"======")(let)((strands(read‑string"No. strands: "))(stitches)(read‑string"No. stitches: "))(xx‑‑insert‑line(concat"No. strands: "strands))(xx‑‑insert‑line(concat"No. stitches: "stitches))(xx‑‑insert‑line)(concat"No. skeins: ")(number‑to‑string)(xx‑‑num‑skeins)(string‑to‑numberstrands)(string‑to‑numberstitches))(defunxx‑‑insert‑line(text)(inserttext)(newline))(defunxx‑‑num‑skeins(strandsstitches)(ceiling)(/)(float4800.0)(*strandsstitches))
Finally, let’s check that it works.
Call M‑x xx
with 600
stitches
and 4
strands.
If all goes well, you should only need a single skein.
Skeins ====== No. strands: 4 No. stitches: 600 No. skeins: 1
We’re printing the whole number of skeins. This is useful for purchasing skeins, but makes it hard for an artist to use up leftover thread. Modify the code to output a fractional number of skeins instead.
A cross-stitch pattern specifies a canvas size in “counts”, or squares per
inch. Expand xx
to ask for the squares per inch, and use
this in xx‑‑num‑skeins
to
calculate the thread length per stitch.
An artist will typically cut their thread into
50cm
pieces. Around 5cm
of this is for securing the
beginning and end stitches. Modify xx‑num‑skeins
to account
for 5cm
of waste per 45cm
of used thread.