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‑numberwith apromptto 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))