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")
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)(/)(float4800.0)(*strandsstitches))
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)(/)(float4800.0)(*strandsstitches))(defunxx‑‑read‑positive(prompt))
xx‑‑read‑positive
should do the following:
Call
read‑number
with aprompt
to get a numeric input.If the input is an integer greater than zero, return it.
If not, write a helpful message to the minibuffer and repeat the process.
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)(/)(float4800.0)(*strandsstitches))(defunxx‑‑read‑positive(prompt)(let)()(input)(read‑numberprompt))
The rest involves some new tools: booleans and the if form.
Booleans
Numbers can be compared to each other using the <
, >
, <=
and <=
functions.
Experiment with these in *scratch*
.
(<34)(>35)
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
Testing if a number is an integer is also a logical test, done with integerp
.
Experiment with the integerp
function.
(integerp5.0)(integerp5)
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.
Type
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.
Combining logical tests
Logical tests can be manipulated using and
, or
and not
.
Experiment with these in *scratch*
.
(and(<34)(integerp3))(or(>35)(integerp3))(notnil)
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)(/)(float4800.0)(*strandsstitches))(defunxx‑‑read‑positive(prompt)(let)((input)(read‑numberprompt))(and)(integerpinput)(>input0))
Introducing the if form
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")
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+++")
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?
Using if
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)(/)(float4800.0)(*strandsstitches))(defunxx‑‑read‑positive(prompt)(let)((input)(read‑numberprompt))(if)(andinput(integerpinput)(>input0))(message"Enter an integer greater than 0."))
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)(/)(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))
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?")
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.
Cleaning up with let*
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)(/)(float4800.0)(*strandsstitches))(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))
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)(/)(float4800.0)(*strandsstitches))(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))