Skip to main content

The Craft of Emacs

Types, errors and control flow

30 minutes

Troubleshoot the xx internals in the *scratch* buffer.

string‑to‑number returns 0 when given a non-numeric input, completely throwing off the skein calculation.

(string‑to‑number"seven")
*scratch*

We can reject the unwanted input as soon as it is entered in the minibuffer by reading numbers instead of strings.

Emacs has a read‑number function for this purpose. It behaves like read‑string, but rejects any non-numeric input.

Use read‑number instead of read‑string.

(defunxx
()
(interactive)
(get‑buffer‑create"*xx*")
(switch‑to‑buffer"*xx*")
(erase‑buffer)
(xx‑‑insert‑line"Skeins")
(xx‑‑insert‑line"======")
(let
(
(strands
(read‑number"No. strands: ")
)
(stitches
(read‑number"No. stitches: ")
)
)
(xx‑‑insert‑line
(concat"No. strands: "
(number‑to‑stringstrands)
)
)
(xx‑‑insert‑line
(concat"No. stitches: "
(number‑to‑stringstitches)
)
)
(xx‑‑insert‑line
(concat"No. skeins: "
(number‑to‑string
(xx‑‑num‑skeinsstrandsstitches)
)
)
)
)
)
(defunxx‑‑insert‑line
(text)
(inserttext)
(newline)
)
(defunxx‑‑num‑skeins
(strandsstitches)
(ceiling
(/
(float
(*strandsstitches)
)
4800.0)
)
)
xx.el

A call to M‑x xx politely nudges us to enter a number.

No. strands:  Three 
Please enter a number.

However, even this has problems.

One can input any number, even negative numbers and decimals. For instance, we can calculate the skeins required for a 0.2 strand pattern comprised of ‑3 stitches.

M‑x xx needs a read function that only accepts positive integers. Emacs doesn’t provide one; it’s time to roll up your sleeves and write it.

Define a xx‑‑read‑positive 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: ")
)
(stitches
(xx‑‑read‑positive"No. stitches: ")
)
)
(xx‑‑insert‑line
(concat"No. strands: "
(number‑to‑stringstrands)
)
)
(xx‑‑insert‑line
(concat"No. stitches: "
(number‑to‑stringstitches)
)
)
(xx‑‑insert‑line
(concat"No. skeins: "
(number‑to‑string
(xx‑‑num‑skeinsstrandsstitches)
)
)
)
)
)
(defunxx‑‑insert‑line
(text)
(inserttext)
(newline)
)
(defunxx‑‑num‑skeins
(strandsstitches)
(ceiling
(/
(float
(*strandsstitches)
)
4800.0)
)
)
(defunxx‑‑read‑positive
(prompt)
)
xx.el

xx‑‑read‑positive should do the following:

Use let to bind the result of read‑number to an input variable.

(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: ")
)
(stitches
(xx‑‑read‑positive"No. stitches: ")
)
)
(xx‑‑insert‑line
(concat"No. strands: "
(number‑to‑stringstrands)
)
)
(xx‑‑insert‑line
(concat"No. stitches: "
(number‑to‑stringstitches)
)
)
(xx‑‑insert‑line
(concat"No. skeins: "
(number‑to‑string
(xx‑‑num‑skeinsstrandsstitches)
)
)
)
)
)
(defunxx‑‑insert‑line
(text)
(inserttext)
(newline)
)
(defunxx‑‑num‑skeins
(strandsstitches)
(ceiling
(/
(float
(*strandsstitches)
)
4800.0)
)
)
(defunxx‑‑read‑positive
(prompt)
(let
(
(input
(read‑numberprompt)
)
)
)
)
xx.el

The rest involves some new tools: booleans and the if form.

Numbers can be compared to each other using the <, >, <= and <= functions.

Experiment with these in *scratch*.

(<34)
(>35)
*scratch*

These statements are logical tests. They return either true or false.

In mathematics lingo, they return boolean values. A boolean is the result of a logical test, and is either true or false.

In Elisp, “true” is represented by the symbol t and “false” by the symbol nil. t and nil are always present in the interpreter’s environment.

Verify that t and nil evaluate to themselves.

nilt
*scratch*

Testing if a number is an integer is also a logical test, done with integerp.

Experiment with the integerp function.

(integerp5.0)
(integerp5)
*scratch*

The “p” in integerp is short for predicate, a function from a single thing to a boolean. Emacs has many predicates for testing data types: stringp, number‑or‑marker‑p and booleanp to name a few.

The term “type” is particularly charged in programming languages and deserves some explanation.

In other languages, a type is a classification of a value. A value can never have two types, and it’s always possible to find out exactly what type a given value has.

Elisp doesn’t have these notions. To the interpreter, a value is of a given type if it satisfies a predicate.

Something is an integer if integerp tests positive, and a string if stringp does. Values can then have multiple types: nil is both a symbol by symbolp and a boolean by booleanp.

the interpreter can’t possibly find out all the types a given value satisfies since anyone can define a predicate. This is why its errors are somewhat ambiguous. Recall the error message from our coe‑xx‑basket‑view experiments:

Wrong type argument: listp, coe-xx-basket-view

The value coe‑xx‑basket‑view is the wrong type. It isn’t a list according to listp. It would be helpful if the interpreter told us that coe‑xx‑basket‑view is a symbol instead of the expected list, but it can’t viably determine this.

Logical tests can be manipulated using and, or and not.

Experiment with these in *scratch*.

(and
(<34)
(integerp3)
)
(or
(>35)
(integerp3)
)
(notnil)
*scratch*

Why are and and or special forms?

The and special form terminates early on encountering a nil, while the or form does the opposite.

Use and to test for integers greater than zero.

(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: ")
)
(stitches
(xx‑‑read‑positive"No. stitches: ")
)
)
(xx‑‑insert‑line
(concat"No. strands: "
(number‑to‑stringstrands)
)
)
(xx‑‑insert‑line
(concat"No. stitches: "
(number‑to‑stringstitches)
)
)
(xx‑‑insert‑line
(concat"No. skeins: "
(number‑to‑string
(xx‑‑num‑skeinsstrandsstitches)
)
)
)
)
)
(defunxx‑‑insert‑line
(text)
(inserttext)
(newline)
)
(defunxx‑‑num‑skeins
(strandsstitches)
(ceiling
(/
(float
(*strandsstitches)
)
4800.0)
)
)
(defunxx‑‑read‑positive
(prompt)
(let
(
(input
(read‑numberprompt)
)
)
(and
(integerpinput)
(>input0)
)
)
)
xx.el

The if form performs a logical test and acts on its result. It does one thing if the test succeeds and another if it fails.

Experiment with if.

(if
(<34)
"Three is less than four""Three is greater than four")
*scratch*

Like all special forms, it has a custom structure.

(ifCONDITIONTHENELSE...)

CONDITION represents the logical test. If it succeeds, the interpreter evaluates the THEN s-expression. If it fails, the remaining ELSE s-expressions are evaluated instead. Strictly speaking, the condition doesn’t need to be a logical test.

Try using a non-nil condition.

(if"Out of Cheese""+++Please Reinstall Universe And Reboot+++""+++MELON MELON MELON+++")
*scratch*

if considers any non-nil condition to be a success; this expression will always result in "+++Please Reinstall Universe And Reboot+++".

How do and and or behave on non-nil arguments?

On an invalid input, the user must be presented with a message in the minibuffer that asks for a positive integer.

Use if and message to print a message for bad inputs.

(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: ")
)
(stitches
(xx‑‑read‑positive"No. stitches: ")
)
)
(xx‑‑insert‑line
(concat"No. strands: "
(number‑to‑stringstrands)
)
)
(xx‑‑insert‑line
(concat"No. stitches: "
(number‑to‑stringstitches)
)
)
(xx‑‑insert‑line
(concat"No. skeins: "
(number‑to‑string
(xx‑‑num‑skeinsstrandsstitches)
)
)
)
)
)
(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.")
)
)
)
xx.el

We can prompt the user for a proper integer again by calling xx‑‑read‑positive itself.

This overrides our previous message in the minibuffer, but the handy sit‑for function will give enough time to read it.

Use xx‑‑read‑positive within itself.

(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: ")
)
(stitches
(xx‑‑read‑positive"No. stitches: ")
)
)
(xx‑‑insert‑line
(concat"No. strands: "
(number‑to‑stringstrands)
)
)
(xx‑‑insert‑line
(concat"No. stitches: "
(number‑to‑stringstitches)
)
)
(xx‑‑insert‑line
(concat"No. skeins: "
(number‑to‑string
(xx‑‑num‑skeinsstrandsstitches)
)
)
)
)
)
(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

A function that calls itself is somewhat mind‑boggling.

Experiment with xx‑‑read‑positive.

(xx‑‑read‑positive"How many stitches can you make in a lifetime?")
*scratch*

Each time we enter a bad input xx‑‑read‑positive is called once more. It keeps being called until a valid input is eventually entered.

Verify that M‑x xx only accepts positive integers.

Armed with input validation, M‑x xx is much more robust. But it’s looking a little untidy, and we’re running the risk of getting tangled in those heavily nested braces.

It would be easier to read if the result of xx‑‑num‑skeins was bound to a skeins variable.

(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: ")
)
(stitches
(xx‑‑read‑positive"No. stitches: ")
)
(skeins
(xx‑‑num‑skeinsstrandsstitches)
)
)
(xx‑‑insert‑line
(concat"No. strands: "
(number‑to‑stringstrands)
)
)
(xx‑‑insert‑line
(concat"No. stitches: "
(number‑to‑stringstitches)
)
)
(xx‑‑insert‑line
(concat"No. skeins: "
(number‑to‑stringskeins)
)
)
)
)
(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

This won’t work.

Recall that each binding must be independent. skeins must be calculated using days and slices‑per‑day, so it cannot be bound using let.

Thankfully, Emacs has another special form for dependent bindings. This is known as let*, pronounced “let star”.

let* has the same structure as let, but its bindings can refer to previous ones.

Use let* instead of let.

(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: ")
)
(stitches
(xx‑‑read‑positive"No. stitches: ")
)
(skeins
(xx‑‑num‑skeinsstrandsstitches)
)
)
(xx‑‑insert‑line
(concat"No. strands: "
(number‑to‑stringstrands)
)
)
(xx‑‑insert‑line
(concat"No. stitches: "
(number‑to‑stringstitches)
)
)
(xx‑‑insert‑line
(concat"No. skeins: "
(number‑to‑stringskeins)
)
)
)
)
(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