fudge
"vanilla"
Explore the demo with M‑x coe‑demo‑xx‑kirby
.
The demo asks for the number of stitches for each colour in the
bakery.xx
pattern. Entering 4 strands
and 1500,
1450 and 2500 stitches respectively gives the correct
number of skeins:
Skeins ====== No. strands: 4 Total no. skeins: 7 No. black skeins : 2 No. magenta skeins : 2 No. pink skeins : 3
Our revamped M‑x xx
command must:
Ask for the number of strands.
For each colour (black, magenta, pink):
Ask for the number of stitches in the pattern.
Calculate the number of skeins to use.
Calculate the total number of skeins.
While seemingly a small step up from our previous command, it uncovers the new quote and lambda special forms.
These tools are more sophisticated than any we’ve previously encountered, so we’ll take some care in exploring them.
Replace xx
with the following stripped down definition.
(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: "))(xx‑‑insert‑line)(concat"No. strands: ")(number‑to‑stringstrands))(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))
This is a close copy of the previous code with nothing we haven’t seen before.
Our new function requires a collection of colour names: black, magenta and pink . We can try and represent this with a list s-expression, but this doesn’t evaluate how we wish.
Evaluate the following list.
(blackmagentapink)
The interpreter evaluates lists as function calls. It attempts to
look up the black
symbol’s function and call it with the
values of magenta
and pink
, none
of which exist in its environment.
Instead of a list that is evaluated, we need an s-expression that evaluates to a list.
Elisp has a special form for just this purpose.
The quote
special form accepts a single s-expression EXP
, termed the quoted s-expression, and evaluates to that s-expression. The s-expression itself isn’t evaluated.
(quoteEXP)
This lack of evaluation is a subtle concept, best understood through experimentation.
Call quote on a non-existent symbol, then step through its evaluation.
Evaluate the list …
(quotecookie)
quote
is a special form.
(quotecookie)
Evaluate the list to cookie
cookie
If the interpreter evaluated cookie
alone, it would throw an error.
But when it evaluates (quote cookie)
, it returns the symbol cookie
. It never searches it’s environment for a variable.
We could try quoting a symbol that has a variable binding.
Does the code below result in fudge
or
"vanilla"
?
…
(let((fudge"vanilla"))(quotefudge))
...
Evaluate the list …
(let((fudge"vanilla"))(quotefudge))
...
let
is a special form.
(let((fudge"vanilla"))(quotefudge))
...
Evaluate the bindings …
(let((fudge"vanilla"))(quotefudge))
...
Evaluate"vanilla"
to "vanilla"
(let((fudge"vanilla"))(quotefudge))
...
Bind fudge
to "vanilla"
(let((fudge"vanilla"))(quotefudge))
fudge
"vanilla"
...
Evaluate the body …
(let((fudge"vanilla"))(quotefudge))
fudge
"vanilla"
...
Evaluate the list …
(let((fudge"vanilla"))(quotefudge))
fudge
"vanilla"
...
quote
is a special form.
(let((fudge"vanilla"))(quotefudge))
fudge
"vanilla"
...
Evaluate the list to fudge
(let(fudge)(fudge"vanilla"))
fudge
"vanilla"
...
Evaluate the list to fudge
fudge
...
We get the symbol fudge
. Even though the symbol is
bound to a variable, the interpreter never looks its value up.
It’s important to stress that the returned fudge
symbol has nothing to
do with "vanilla"
. The binding between
fudge
and "vanilla"
was forgotten after the
let
special form was evaluated, and was never
actually used. The fudge
returned is a value
, not a variable name.
Through quote
we can use symbols as values,
passing them as arguments to functions.
Find the name of a symbol by passing it to the symbol‑name
function
…
(symbol‑name(quoteginger‑nut))
Evaluate the list …
(symbol‑name(quoteginger‑nut))
symbol‑name
is a function.
(symbol‑name(quoteginger‑nut))
Evaluate the list …
(symbol‑name(quoteginger‑nut))
quote
is a special form.
(symbol‑name(quoteginger‑nut))
Evaluate the list to ginger‑nut
(symbol‑nameginger‑nut)
Evaluate the list to "ginger‑nut"
"ginger‑nut"
We can even bind symbols as values using let
.
Which symbol’s name is printed by the following code?
…
(let((thin)(quotemint))(symbol‑namethin))
...
Evaluate the list …
(let((thin)(quotemint))(symbol‑namethin))
...
let
is a special form.
(let((thin)(quotemint))(symbol‑namethin))
...
Evaluate the bindings …
(let((thin)(quotemint))(symbol‑namethin))
...
Evaluate the list …
(let((thin)(quotemint))(symbol‑namethin))
...
quote
is a special form.
(let((thin)(quotemint))(symbol‑namethin))
...
Evaluate the list to mint
(let((thinmint))(symbol‑namethin))
...
Bind thin
to mint
(let((thinmint))(symbol‑namethin))
thin
mint
...
Evaluate the body …
(let((thinmint))(symbol‑namethin))
thin
mint
...
Evaluate the list …
(let((thinmint))(symbol‑namethin))
thin
mint
...
symbol‑name
is a function.
(let((thinmint))(symbol‑namethin))
thin
mint
...
Evaluatethin
to mint
(let((thinmint))(symbol‑namemint))
thin
mint
...
Evaluate the list to "mint"
(let("mint")(thinmint))
thin
mint
...
Evaluate the list to "mint"
"mint"
...
Symbols have been somewhat enigmatic along our Elisp journey.
When we first discovered them, we could only use them as function names. Shortly after, we found they could be bound to variables and used to look up values.
In both cases, the interpreter evaluated the symbol by looking it up in its environment. And in most languages, that’s all symbols are for: they always represent a value or a function, or possibly a keyword of some kind.
But this is Elisp, and symbols can surprise us. Through the use of quote, they don’t need to represent anything at all. A symbol can be treated as a value, just as a string or number.
Unknowingly, we’ve already encountered symbols as values. The boolean variables t
and nil
both evaluate to their respective symbols.
There’s no difference between their evaluated and unevaluated value: t
evaluates to t
, and nil
likewise. This means that leaving a boolean unevaluated through quoting has no real effect.
Verify this using the equal
function.
(equal(quotenil)nil)
Both s-expressions result in nil
, so the values are equal.
Like booleans — numbers and strings evaluate to themselves.
Verify this.
(equal(quote42)42)(equal(quote"scone")"scone")
Quoting numbers, strings and booleans is a worthwhile experiment, but isn’t useful in itself. You shouldn’t need to do so in real code.
Try quoting a list.
(quote(lemontart))
The interpreter outputs the list, leaving it and the s-expressions within it unevaluated.
Can an empty list be quoted?
(quote())
This is successful, albeit with the mysterious result of
nil
. This mystery will be revealed in the next chapter.
What about a nested list?
(quote(pearand)(rhubarbcrumble))
The s-expression (rhubarb crumble)
is treated as a list of symbols, none of which correspond to functions or variables.
As with symbols, quoting lets us use lists as values. For our purpose, we can use a quoted list of colours.
(quote(blackmagentapink))
Elisp code makes such heavy use of quote
that there’s a syntactic shorthand for it — the apostrophe '
.
Use '
instead of quote
.
…
'macaroon
Read the s-expression
(quotemacaroon)
Evaluate the list …
(quotemacaroon)
quote
is a special form.
(quotemacaroon)
Evaluate the list to macaroon
macaroon
The interpreter reads 'macaroon
as
(quote macaroon)
prior to evaluation, so the
quote
special form is evaluated exactly the same.
All s-expressions can be quoted using the shorthand syntax.
Use '
to quote a list.
…
'
(banoffeepie)
Read the s-expression
(quote(banoffeepie))
Evaluate the list …
(quote(banoffeepie))
quote
is a special form.
(quote(banoffeepie))
Evaluate the list to (banoffee pie)
(banoffeepie)
Somewhat confusingly, the '
character can be used within quoted s-expressions.
Use '
within a quoted list.
…
'
(sponge'pudding)
Read the s-expression
(quote(sponge)(quotepudding))
Evaluate the list …
(quote(sponge)(quotepudding))
quote
is a special form.
(quote(sponge)(quotepudding))
Evaluate the list to (sponge (quote pudding))
(sponge(quotepudding))
The quote
in (quote pudding)
is a symbol like any other, and is not evaluated.
Nesting quotes within quotes is usually a mistake. It’s easy to make and hard to spot, so be sure to check your quoted s-expressions.
As an apostrophe is much easier on the eyes, we’ll favour it instead of writing quote
explicitly.
With quote
at hand, we can make a start on our xx
function.
Add a quoted list of colours to xx
.
(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')(blackmagentapink))(xx‑‑insert‑line)(concat"No. strands: ")(number‑to‑stringstrands))(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))
Next, we need to ask for the number of stitches for each colour in the list.
This can be done using the seq‑map
function.
seq‑map
accepts two arguments: a symbol corresponding to a
function, and a list. When evaluated, the interpreter looks up the
function and applies it to each element in the list.
Use seq‑map
to compute a list of string lengths.
(seq‑map'length'("Rich tea""custard cream""Bourbon"))
Use seq‑map
to handle each colour with a xx‑‑read‑stitches
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'(blackmagentapink))(stitches)(seq‑map'xx‑‑read‑stitchescolours))(xx‑‑insert‑line)(concat"No. strands: ")(number‑to‑stringstrands))(defunxx‑‑read‑stitches(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))
For each colour, xx‑‑read‑stitches
should ask
for the number of stitches.
Implement it using xx‑‑read‑positive
.
(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'(blackmagentapink))(stitches)(seq‑map'xx‑‑read‑stitchescolours))(xx‑‑insert‑line)(concat"No. strands: ")(number‑to‑stringstrands))(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))
Verify that M‑x xx
asks for the number of stitches for
each colour.
Our next task is to calculate the number of skeins of each colour. We
already wrote a xx‑‑num‑skeins
function to do this — Let’s try and use it.
Can you calculate the number of skeins per colour using seq‑map
?
(seq‑map'xx‑‑num‑stitches'(245))
This function doesn’t work well with seq‑map
.
The function used to handle each element must accept a single
argument, but xx‑‑num‑skeins
needs two.
While we can’t use xx‑‑num‑skeins
directly, we can
construct the function we need using the lambda special form.
Exploring quote
and seq‑map
in one sitting is a
feat worthy of celebration. If you’ve managed it, I admire your persistence and
dedication.
lambda
is a little more complex. To
give it your full attention you may first need a cup of tea, a
biscuit and possibly a short nap. Once revigorated, proceed onwards.
The lambda
special form is similar to defun
and is used to construct functions.
(lambda(ARGS...)BODY...)
Unlike defun
, the functions it constructs don’t have names and aren’t automatically added to the environment. Due to their namelessness, they are termed anonymous functions.
As with normal functions, anonymous functions can accept any number of arguments and have any number of body statements.
The anonymous function constructed below accepts a single snack
argument.
(lambda(snack)(read‑string)(concat"Would you like "snack"? "))
The simplest way of calling it is by placing it at the start of the list. The interpreter evaluates it to produce a function, and calls that function with the remaining list elements.
Call the anonymous function.
…
((lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
...
Evaluate the list …
((lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
...
Evaluate the list …
((lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
...
lambda
is a special form.
((lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
...
Evaluate the list to an anonymous function.
((lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
...
Evaluate"biscuits"
to "biscuits"
((lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
...
Evaluate the bindings …
((lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
...
Bind snack
to "biscuits"
((lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
snack
"biscuits"
...
Evaluate the body …
((lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
snack
"biscuits"
...
Evaluate the list …
((lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
snack
"biscuits"
...
read‑string
is a function.
((lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
snack
"biscuits"
...
Evaluate the list …
((lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
snack
"biscuits"
...
concat
is a function.
((lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
snack
"biscuits"
...
Evaluate"Would you like "
to "Would you like "
((lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
snack
"biscuits"
...
Evaluatesnack
to "biscuits"
((lambda"biscuits")(snack)(read‑string)(concat"Would you like ""biscuits""? "))
snack
"biscuits"
...
Evaluate"? "
to "? "
((lambda"biscuits")(snack)(read‑string)(concat"Would you like ""biscuits""? "))
snack
"biscuits"
...
Evaluate the list to "Would you like biscuits? "
((lambda"biscuits")(snack)(read‑string"Would you like biscuits? "))
snack
"biscuits"
...
Evaluate the list to "Of course!"
((lambda"biscuits")(snack)"Of course!")
snack
"biscuits"
...
Evaluate the list to "Of course!"
"Of course!"
...
Calling an anonymous function like this isn’t useful — we would have got the same effect had we written the body code.
The funcall
function is more versatile. It accepts a function as its first argument and the
function’s arguments as the rest.
Call the anonymous function with funcall
.
…
(funcall(lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
...
Evaluate the list …
(funcall(lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
...
funcall
is a function.
(funcall(lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
...
Evaluate the list …
(funcall(lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
...
lambda
is a special form.
(funcall(lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
...
Evaluate the list to an anonymous function.
(funcall(lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
...
Evaluate"biscuits"
to "biscuits"
(funcall(lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
...
Call the funcall
function.
((lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
...
Evaluate the bindings …
((lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
...
Bind snack
to "biscuits"
((lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
snack
"biscuits"
...
Evaluate the body …
((lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
snack
"biscuits"
...
Evaluate the list …
((lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
snack
"biscuits"
...
read‑string
is a function.
((lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
snack
"biscuits"
...
Evaluate the list …
((lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
snack
"biscuits"
...
concat
is a function.
((lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
snack
"biscuits"
...
Evaluate"Would you like "
to "Would you like "
((lambda"biscuits")(snack)(read‑string)(concat"Would you like "snack"? "))
snack
"biscuits"
...
Evaluatesnack
to "biscuits"
((lambda"biscuits")(snack)(read‑string)(concat"Would you like ""biscuits""? "))
snack
"biscuits"
...
Evaluate"? "
to "? "
((lambda"biscuits")(snack)(read‑string)(concat"Would you like ""biscuits""? "))
snack
"biscuits"
...
Evaluate the list to "Would you like biscuits?"
((lambda"biscuits")(snack)(read‑string"Would you like biscuits?"))
snack
"biscuits"
...
Evaluate the list to "Of course!"
((lambda"biscuits")(snack)"Of course!")
snack
"biscuits"
...
Evaluate the list to "Of course!"
"Of course!"
...
Since funcall
accepts functions as arguments, it enables
us to pass functions around.
Write the function tea
that asks about tea and
biscuits.
(defuntea(ask‑function)(concat)(funcallask‑function"milk")", "(funcallask‑function"sugar")" and "(funcallask‑function"biscuits"))
tea
accepts a single ask‑function
argument. When called, it uses funcall
to call that function
multiple times.
Call tea
with the anonymous function.
(tea(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))
funcall
and lambda
allow us to pass functions around as values. When combined with
let
, these functions can be bound to variables.
Use let
to bind the symbol
ask‑function
to an anonymous function.
…
(let((ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))(funcallask‑function"Earl Grey"))
...
Evaluate the list …
(let((ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))(funcallask‑function"Earl Grey"))
...
let
is a special form.
(let((ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))(funcallask‑function"Earl Grey"))
...
Evaluate the bindings …
(let((ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))(funcallask‑function"Earl Grey"))
...
Evaluate the list …
(let((ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))(funcallask‑function"Earl Grey"))
...
lambda
is a special form.
(let((ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))(funcallask‑function"Earl Grey"))
...
Evaluate the list to an anonymous function.
(let((ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))(funcallask‑function"Earl Grey"))
...
Bind ask‑function
to (lambda (snack) ...)
(let((ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))(funcallask‑function"Earl Grey"))
ask-function
(lambda (snack) ...)
...
Evaluate the body …
(let((ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))(funcallask‑function"Earl Grey"))
ask-function
(lambda (snack) ...)
...
funcall
is a function.
(let((ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))(funcallask‑function"Earl Grey"))
ask-function
(lambda (snack) ...)
...
Evaluateask‑function
to (lambda (snack) ...)
(let((ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))(funcall)(lambda"Earl Grey")(snack)(read‑string)(concat"Would you like "snack"? "))
ask-function
(lambda (snack) ...)
...
Evaluate"Earl Grey"
to "Earl Grey"
(let((ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))(funcall)(lambda"Earl Grey")(snack)(read‑string)(concat"Would you like "snack"? "))
ask-function
(lambda (snack) ...)
...
Call the funcall
function.
(let((ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))()(lambda"Earl Grey")(snack)(read‑string)(concat"Would you like "snack"? "))
ask-function
(lambda (snack) ...)
...
Evaluate the bindings …
(let((ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))()(lambda"Earl Grey")(snack)(read‑string)(concat"Would you like "snack"? "))
ask-function
(lambda (snack) ...)
...
Bind snack
to "Earl Grey"
(let((ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))()(lambda"Earl Grey")(snack)(read‑string)(concat"Would you like "snack"? "))
ask-function
(lambda (snack) ...)
snack
"Earl Grey"
...
Evaluate the body …
(let((ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))()(lambda"Earl Grey")(snack)(read‑string)(concat"Would you like "snack"? "))
ask-function
(lambda (snack) ...)
snack
"Earl Grey"
...
Evaluate the list …
(let((ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))()(lambda"Earl Grey")(snack)(read‑string)(concat"Would you like "snack"? "))
ask-function
(lambda (snack) ...)
snack
"Earl Grey"
...
read‑string
is a function.
(let((ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))()(lambda"Earl Grey")(snack)(read‑string)(concat"Would you like "snack"? "))
ask-function
(lambda (snack) ...)
snack
"Earl Grey"
...
Evaluate the list …
(let((ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))()(lambda"Earl Grey")(snack)(read‑string)(concat"Would you like "snack"? "))
ask-function
(lambda (snack) ...)
snack
"Earl Grey"
...
concat
is a function.
(let((ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))()(lambda"Earl Grey")(snack)(read‑string)(concat"Would you like "snack"? "))
ask-function
(lambda (snack) ...)
snack
"Earl Grey"
...
Evaluate"Would you like "
to "Would you like "
(let((ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))()(lambda"Earl Grey")(snack)(read‑string)(concat"Would you like "snack"? "))
ask-function
(lambda (snack) ...)
snack
"Earl Grey"
...
Evaluatesnack
to "Earl Grey"
(let((ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))()(lambda"Earl Grey")(snack)(read‑string)(concat"Would you like ""Earl Grey""? "))
ask-function
(lambda (snack) ...)
snack
"Earl Grey"
...
Evaluate the list to "Would you like Earl Grey? "
(let((ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))()(lambda"Earl Grey")(snack)(read‑string"Would you like Earl Grey? "))
ask-function
(lambda (snack) ...)
snack
"Earl Grey"
...
Evaluate the list to "No, thank you."
(let((ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))()(lambda"Earl Grey")(snack)"No, thank you.")
ask-function
(lambda (snack) ...)
snack
"Earl Grey"
...
Evaluate the list to "No, thank you."
(let("No, thank you.")(ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))
ask-function
(lambda (snack) ...)
...
Evaluate the list to "No, thank you."
"No, thank you."
...
Can ask‑function
be called like a named function?
(let((ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))(ask‑function"Earl Grey"))
The interpreter can’t find the function
definition for ask‑function
.
Recall that function and variable names are stored separately in
the environment. The ask‑function
symbol’s variable is
bound to the anonymous function, but not its function. The interpreter
tries to look up its function, and fails to
find it.
Can the ask‑function
symbol be quoted when
passed to funcall
?
(let((ask‑function)(lambda)(snack)(read‑string)(concat"Would you like "snack"? "))(funcall'ask‑function"Earl Grey"))
When funcall
is passed a symbol, it attempts to look up
its function binding, not its variable binding.
Verify this with a function binding.
(funcall'read‑string"Would you like Earl Grey? ")
The ask‑function
symbol’s variable is bound to an
anonymous function, whereas the read‑string
symbol has a
function defined for it.
ask
(lambda ...)
read-string
instructions...
...
Passing functions around is a useful skill, but it doesn’t help us write a single-argument function.
To do this, we need to explore some of the other properties of lambda
.
Write an ask‑nicely
function.
(defunask‑nicely(endearment)(lambda)(snack)(read‑string)(concat"Would you like "snack", "endearment"? "))
ask‑nicely
accepts a string endearment
and
returns an anonymous function. The returned function,
when called with a snack
,
asks a question.
Call the returned function with funcall
.
…
(funcall(ask‑nicely"dearie")"teacakes")
...
Evaluate the list …
(funcall(ask‑nicely"dearie")"teacakes")
...
funcall
is a function.
(funcall(ask‑nicely"dearie")"teacakes")
...
Evaluate the list …
(funcall(ask‑nicely"dearie")"teacakes")
...
ask‑nicely
is a function.
(funcall(ask‑nicely"dearie")"teacakes")
...
Evaluate"dearie"
to "dearie"
(funcall(ask‑nicely"dearie")"teacakes")
...
Evaluate the list to an anonymous function.
(funcall(lambda"teacakes")(snack)(read‑string)(concat"Would you like "snack", "endearment"? "))
...
Evaluate"teacakes"
to "teacakes"
(funcall(lambda"teacakes")(snack)(read‑string)(concat"Would you like "snack", "endearment"? "))
...
Call the funcall
function.
((lambda"teacakes")(snack)(read‑string)(concat"Would you like "snack", "endearment"? "))
endearment
"dearie"
...
Evaluate the bindings …
((lambda"teacakes")(snack)(read‑string)(concat"Would you like "snack", "endearment"? "))
endearment
"dearie"
...
Bind snack
to "teacakes"
((lambda"teacakes")(snack)(read‑string)(concat"Would you like "snack", "endearment"? "))
endearment
"dearie"
snack
"teacakes"
...
Evaluate the body …
((lambda"teacakes")(snack)(read‑string)(concat"Would you like "snack", "endearment"? "))
endearment
"dearie"
snack
"teacakes"
...
Evaluate the list …
((lambda"teacakes")(snack)(read‑string)(concat"Would you like "snack", "endearment"? "))
endearment
"dearie"
snack
"teacakes"
...
read‑string
is a function.
((lambda"teacakes")(snack)(read‑string)(concat"Would you like "snack", "endearment"? "))
endearment
"dearie"
snack
"teacakes"
...
Evaluate the list …
((lambda"teacakes")(snack)(read‑string)(concat"Would you like "snack", "endearment"? "))
endearment
"dearie"
snack
"teacakes"
...
concat
is a function.
((lambda"teacakes")(snack)(read‑string)(concat"Would you like "snack", "endearment"? "))
endearment
"dearie"
snack
"teacakes"
...
Evaluate"Would you like "
to "Would you like "
((lambda"teacakes")(snack)(read‑string)(concat"Would you like "snack", "endearment"? "))
endearment
"dearie"
snack
"teacakes"
...
Evaluatesnack
to "teacakes"
((lambda"teacakes")(snack)(read‑string)(concat"Would you like ""teacakes"", "endearment"? "))
endearment
"dearie"
snack
"teacakes"
...
Evaluate", "
to ", "
((lambda"teacakes")(snack)(read‑string)(concat"Would you like ""teacakes"", "endearment"? "))
endearment
"dearie"
snack
"teacakes"
...
Evaluateendearment
to "dearie"
((lambda"teacakes")(snack)(read‑string)(concat"Would you like ""teacakes"", ""dearie""? "))
endearment
"dearie"
snack
"teacakes"
...
Evaluate"? "
to "? "
((lambda"teacakes")(snack)(read‑string)(concat"Would you like ""teacakes"", ""dearie""? "))
endearment
"dearie"
snack
"teacakes"
...
Evaluate the list to "Would you like teacakes, dearie? "
((lambda"teacakes")(snack)(read‑string"Would you like teacakes, dearie? "))
endearment
"dearie"
snack
"teacakes"
...
Evaluate the list to "Yes, please"
((lambda"teacakes")(snack)"Yes, please")
endearment
"dearie"
snack
"teacakes"
...
Evaluate the list to "Yes, please"
"Yes, please"
...
Looking closely, the interpreter’s environment is a bit peculiar.
The endearment
symbol is bound within the body of
ask‑nicely
. When ask‑nicely
was called, the symbol was
bound to "dearie"
and the body was evaluated to create
an anonymous function. That anonymous function was returned.
Any further lookups of endearment
would usually throw an
error — the symbol is no longer present in the interpreter’s environment.
How was it bound later?
Thus far, we’ve thought of the environment as the interpreter’s dictionary.
The interpreter adds symbols to the dictionary on finding the defun
or let
forms, and
erases those symbols once the forms have been left.
But ask‑nicely
showed that the environment is much more nuanced.
Each anonymous function carries its own dictionary of symbols that were available when it was defined. In other words, they have their own environments.
In programming speak, an anonymous function “closes over its environment”. For this reason, anonymous functions are sometimes known as closures.
Use lambda
in xx
to close over the strands
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: "))(colours'(blackmagentapink))(stitches(seq‑map'xx‑‑read‑stitchescolours))(num‑skeins‑function(lambda)(count)(xx‑‑num‑skeinsstrandscount))(num‑skeins)(seq‑mapnum‑skeins‑functionstitches))(xx‑‑insert‑line)(concat"No. strands: ")(number‑to‑stringstrands))(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))
The conventional ‑function
suffix is a sign that the
num‑skeins‑function
variable is bound to a function.
Mapping over the stitches
with it results in a list of the
number of skeins for each colour.
Check that M‑x xx
is still functional.
We next need to calculate the total number of skeins by summing up the number of each colour.
We’ve already seen the +
function. It’s a bit
special compared to the ones we’ve defined ourselves: it can accept
any number of arguments.
(+)(+12)(+456)
We’d like to use it to sum up all the numbers in the
num‑skeins
list.
Attempt to use +
on a list of numbers.
(+'(123))
As you might expect, +
accepts numeric arguments. We
can’t simply call it with a list and expect it to output what we want.
We need to pass a each item in the list as an argument to
+
. Elisp has just the function for the task: apply
.
apply
accepts a function name and a list to use as
arguments. It passes the arguments to the given function:
(apply'+'(123))
Calculate the total number of skeins with +
and
apply
.
(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'(blackmagentapink))(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))(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))
Verify that M‑x xx
prints the total number of skeins.
Finally, we must insert the number of skeins per colour into the buffer.
To do this, we need to use the name of each colour as well as the number of skeins.
The seq‑map
function isn’t helpful here — it only maps
over a single list, but we need to operate on both the list of names
and the list of numbers.
We need to find a function similar to seq‑map
.
So far, I’ve simply told you the function names to use. This makes for
speedy development, but in practice finding the function name is half the job.
Instead, let’s take this opportunity to test your own exploration skills.
There are a few places we could look. We’ll go through each of these in turn:
The description of seq‑map
might link to related functions.
The Emacs manual might have an entry on seq‑map
that
might describe related functions.
The file that seq‑map
is located in might contain the function we want.
Call M‑x describe‑function seq‑map
.
seq-map is a byte-compiled Lisp function in `seq.el'. (seq-map FUNCTION SEQUENCE) Return the result of applying FUNCTION to each element of SEQUENCE. Other relevant functions are documented in the sequence group. ...
The description does indeed link to other functions.
Click on sequence
.
Sequence Predicates (seq-contains-p sequence elt &optional testfn) Return non-nil if SEQUENCE contains an element equal to ELT. (seq-contains-p '(a b c) 'b) => t (seq-contains-p '(a b c) 'd) => ni ...
The buffer lists a summary of all sequence-related functions. You can find other summaries through the Menu Bar.
In the Menu Bar, navigate to Help
,
Describe
, Function Group Overview
and type
sequence
.
There are a wide variety of useful functions in the summary. Try
the ones that pique your interest in the *scratch*
buffer.
Is the function we need in the summary?
Don’t look too hard — unfortunately none of the functions suit our purposes.
Our next point of call is the Emacs manual. You can access this
from any Emacs Lisp buffer through the M‑x info‑lookup‑symbol
command.
With your cursor in the xx.el
file, call
M‑x info‑lookup‑symbol seq‑map
.
-- Function: seq-map function sequence This function returns the result of applying FUNCTION to each element of SEQUENCE. The returned value is a list. (seq-map #'1+ '(2 4 6)) ⇒ (3 5 7) (seq-map #'symbol-name [foo bar]) ⇒ ("foo" "bar") ...
The manual describes seq‑map
and other sequence
functions in a wider context.
Is there a keybinding for M‑x info‑lookup‑symbol
?
You can find out with M‑x describe‑function
. The keybinding
is much quicker to use than typing out the function name.
Is the function we need described in the manual?
A couple of entries down from seq‑map
you’ll find the
elusive seq‑mapn
function.
This accepts a function with any number of arguments and the same number of lists to “map” and uses the function to combine the elements. The manual shows an example usage — sure enough, it’s the function we need.
Before moving on let’s take a look at the seq‑map
source
code. If we couldn’t find anything in the manual we would have needed
to hunt through it.
View the source file with M‑x find‑function seq‑map
.
The source code is far more complicated than the summary or manual; the function definitions use many more Elisp features than you’re familiar with. Try not to be too daunted by digging through the weeds: you don’t need to understand all the syntax at once, and you certainly don’t need to know it all now. Be aware that it exists and that you’ll eventually be able to read through it.
We can complete M‑x xx
by adding a call to seq‑mapn
.
Use seq‑mapn
to insert the number of skeins for
each 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'(blackmagentapink))(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))
Let’s give it a spin.
Call M‑x xx
with:
4
strands
1500
black stitches
1450
magenta stitches
2500
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