Skip to main content

The Craft of Emacs

Quotes, lists and lambdas

1 hour 30 minutes

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
*xx*

Our revamped M‑x xx command must:

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
(/
(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 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)
*scratch*

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.

Symbols

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
variable
"vanilla"
...

Evaluate the body …

(let
(
(fudge"vanilla")
)
(quotefudge)
)
fudge
variable
"vanilla"
...

Evaluate the list …

(let
(
(fudge"vanilla")
)
(quotefudge)
)
fudge
variable
"vanilla"
...

quote is a special form.

(let
(
(fudge"vanilla")
)
(quotefudge)
)
fudge
variable
"vanilla"
...

Evaluate the list to fudge

(let
(
(fudge"vanilla")
)
fudge)
fudge
variable
"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
variable
mint
...

Evaluate the body …

(let
(
(thinmint)
)
(symbol‑namethin)
)
thin
variable
mint
...

Evaluate the list …

(let
(
(thinmint)
)
(symbol‑namethin)
)
thin
variable
mint
...

symbol‑name is a function.

(let
(
(thinmint)
)
(symbol‑namethin)
)
thin
variable
mint
...

Evaluatethin to mint

(let
(
(thinmint)
)
(symbol‑namemint)
)
thin
variable
mint
...

Evaluate the list to "mint"

(let
(
(thinmint)
)
"mint")
thin
variable
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.

Booleans

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)
*scratch*

Both s-expressions result in nil, so the values are equal.

Numbers and strings

Like booleans — numbers and strings evaluate to themselves.

Verify this.

(equal
(quote42)
42)
(equal
(quote"scone")
"scone")
*scratch*

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.

Lists

Try quoting a list.

(quote
(lemontart)
)
*scratch*

The interpreter outputs the list, leaving it and the s-expressions within it unevaluated.

Can an empty list be quoted?

(quote
()
)
*scratch*

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)
)
)
*scratch*

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)
)
*scratch*

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
(/
(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

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")
)
*scratch*

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
(/
(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

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
(/
(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

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)
)
*scratch*

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"? ")
)
)
*scratch*

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
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
...

Evaluate the list …

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
...

Evaluate the list …

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
...

lambda is a special form.

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
...

Evaluate the list to an anonymous function.

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
...

Evaluate"biscuits" to "biscuits"

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
...

Evaluate the bindings …

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
...

Bind snack to "biscuits"

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
snack
variable
"biscuits"
...

Evaluate the body …

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
snack
variable
"biscuits"
...

Evaluate the list …

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
snack
variable
"biscuits"
...

read‑string is a function.

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
snack
variable
"biscuits"
...

Evaluate the list …

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
snack
variable
"biscuits"
...

concat is a function.

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
snack
variable
"biscuits"
...

Evaluate"Would you like " to "Would you like "

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
snack
variable
"biscuits"
...

Evaluatesnack to "biscuits"

(
(lambda
(snack)
(read‑string
(concat"Would you like ""biscuits""? ")
)
)
"biscuits")
snack
variable
"biscuits"
...

Evaluate"? " to "? "

(
(lambda
(snack)
(read‑string
(concat"Would you like ""biscuits""? ")
)
)
"biscuits")
snack
variable
"biscuits"
...

Evaluate the list to "Would you like biscuits? "

(
(lambda
(snack)
(read‑string"Would you like biscuits? ")
)
"biscuits")
snack
variable
"biscuits"
...

Evaluate the list to "Of course!"

(
(lambda
(snack)
"Of course!")
"biscuits")
snack
variable
"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
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
...

Evaluate the list …

(funcall
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
...

funcall is a function.

(funcall
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
...

Evaluate the list …

(funcall
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
...

lambda is a special form.

(funcall
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
...

Evaluate the list to an anonymous function.

(funcall
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
...

Evaluate"biscuits" to "biscuits"

(funcall
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
...

Call the funcall function.

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
...

Evaluate the bindings …

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
...

Bind snack to "biscuits"

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
snack
variable
"biscuits"
...

Evaluate the body …

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
snack
variable
"biscuits"
...

Evaluate the list …

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
snack
variable
"biscuits"
...

read‑string is a function.

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
snack
variable
"biscuits"
...

Evaluate the list …

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
snack
variable
"biscuits"
...

concat is a function.

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
snack
variable
"biscuits"
...

Evaluate"Would you like " to "Would you like "

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"biscuits")
snack
variable
"biscuits"
...

Evaluatesnack to "biscuits"

(
(lambda
(snack)
(read‑string
(concat"Would you like ""biscuits""? ")
)
)
"biscuits")
snack
variable
"biscuits"
...

Evaluate"? " to "? "

(
(lambda
(snack)
(read‑string
(concat"Would you like ""biscuits""? ")
)
)
"biscuits")
snack
variable
"biscuits"
...

Evaluate the list to "Would you like biscuits?"

(
(lambda
(snack)
(read‑string"Would you like biscuits?")
)
"biscuits")
snack
variable
"biscuits"
...

Evaluate the list to "Of course!"

(
(lambda
(snack)
"Of course!")
"biscuits")
snack
variable
"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")
)
)
*scratch*

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"? ")
)
)
)
*scratch*

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
variable
(lambda (snack) ...)
...

Evaluate the body …

(let
(
(ask‑function
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
)
)
(funcallask‑function"Earl Grey")
)
ask-function
variable
(lambda (snack) ...)
...

funcall is a function.

(let
(
(ask‑function
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
)
)
(funcallask‑function"Earl Grey")
)
ask-function
variable
(lambda (snack) ...)
...

Evaluateask‑function to (lambda (snack) ...)

(let
(
(ask‑function
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
)
)
(funcall
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"Earl Grey")
)
ask-function
variable
(lambda (snack) ...)
...

Evaluate"Earl Grey" to "Earl Grey"

(let
(
(ask‑function
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
)
)
(funcall
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"Earl Grey")
)
ask-function
variable
(lambda (snack) ...)
...

Call the funcall function.

(let
(
(ask‑function
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
)
)
(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"Earl Grey")
)
ask-function
variable
(lambda (snack) ...)
...

Evaluate the bindings …

(let
(
(ask‑function
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
)
)
(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"Earl Grey")
)
ask-function
variable
(lambda (snack) ...)
...

Bind snack to "Earl Grey"

(let
(
(ask‑function
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
)
)
(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"Earl Grey")
)
ask-function
variable
(lambda (snack) ...)
snack
variable
"Earl Grey"
...

Evaluate the body …

(let
(
(ask‑function
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
)
)
(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"Earl Grey")
)
ask-function
variable
(lambda (snack) ...)
snack
variable
"Earl Grey"
...

Evaluate the list …

(let
(
(ask‑function
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
)
)
(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"Earl Grey")
)
ask-function
variable
(lambda (snack) ...)
snack
variable
"Earl Grey"
...

read‑string is a function.

(let
(
(ask‑function
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
)
)
(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"Earl Grey")
)
ask-function
variable
(lambda (snack) ...)
snack
variable
"Earl Grey"
...

Evaluate the list …

(let
(
(ask‑function
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
)
)
(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"Earl Grey")
)
ask-function
variable
(lambda (snack) ...)
snack
variable
"Earl Grey"
...

concat is a function.

(let
(
(ask‑function
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
)
)
(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"Earl Grey")
)
ask-function
variable
(lambda (snack) ...)
snack
variable
"Earl Grey"
...

Evaluate"Would you like " to "Would you like "

(let
(
(ask‑function
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
)
)
(
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
"Earl Grey")
)
ask-function
variable
(lambda (snack) ...)
snack
variable
"Earl Grey"
...

Evaluatesnack to "Earl Grey"

(let
(
(ask‑function
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
)
)
(
(lambda
(snack)
(read‑string
(concat"Would you like ""Earl Grey""? ")
)
)
"Earl Grey")
)
ask-function
variable
(lambda (snack) ...)
snack
variable
"Earl Grey"
...

Evaluate the list to "Would you like Earl Grey? "

(let
(
(ask‑function
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
)
)
(
(lambda
(snack)
(read‑string"Would you like Earl Grey? ")
)
"Earl Grey")
)
ask-function
variable
(lambda (snack) ...)
snack
variable
"Earl Grey"
...

Evaluate the list to "No, thank you."

(let
(
(ask‑function
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
)
)
(
(lambda
(snack)
"No, thank you.")
"Earl Grey")
)
ask-function
variable
(lambda (snack) ...)
snack
variable
"Earl Grey"
...

Evaluate the list to "No, thank you."

(let
(
(ask‑function
(lambda
(snack)
(read‑string
(concat"Would you like "snack"? ")
)
)
)
)
"No, thank you.")
ask-function
variable
(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")
)
*scratch*

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")
)
*scratch*

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? ")
*scratch*

The ask‑function symbol’s variable is bound to an anonymous function, whereas the read‑string symbol has a function defined for it.

ask
variable
(lambda ...)
read-string
function
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"? ")
)
)
)
*scratch*

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
(snack)
(read‑string
(concat"Would you like "snack", "endearment"? ")
)
)
"teacakes")
...

Evaluate"teacakes" to "teacakes"

(funcall
(lambda
(snack)
(read‑string
(concat"Would you like "snack", "endearment"? ")
)
)
"teacakes")
...

Call the funcall function.

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack", "endearment"? ")
)
)
"teacakes")
endearment
variable
"dearie"
...

Evaluate the bindings …

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack", "endearment"? ")
)
)
"teacakes")
endearment
variable
"dearie"
...

Bind snack to "teacakes"

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack", "endearment"? ")
)
)
"teacakes")
endearment
variable
"dearie"
snack
variable
"teacakes"
...

Evaluate the body …

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack", "endearment"? ")
)
)
"teacakes")
endearment
variable
"dearie"
snack
variable
"teacakes"
...

Evaluate the list …

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack", "endearment"? ")
)
)
"teacakes")
endearment
variable
"dearie"
snack
variable
"teacakes"
...

read‑string is a function.

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack", "endearment"? ")
)
)
"teacakes")
endearment
variable
"dearie"
snack
variable
"teacakes"
...

Evaluate the list …

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack", "endearment"? ")
)
)
"teacakes")
endearment
variable
"dearie"
snack
variable
"teacakes"
...

concat is a function.

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack", "endearment"? ")
)
)
"teacakes")
endearment
variable
"dearie"
snack
variable
"teacakes"
...

Evaluate"Would you like " to "Would you like "

(
(lambda
(snack)
(read‑string
(concat"Would you like "snack", "endearment"? ")
)
)
"teacakes")
endearment
variable
"dearie"
snack
variable
"teacakes"
...

Evaluatesnack to "teacakes"

(
(lambda
(snack)
(read‑string
(concat"Would you like ""teacakes"", "endearment"? ")
)
)
"teacakes")
endearment
variable
"dearie"
snack
variable
"teacakes"
...

Evaluate", " to ", "

(
(lambda
(snack)
(read‑string
(concat"Would you like ""teacakes"", "endearment"? ")
)
)
"teacakes")
endearment
variable
"dearie"
snack
variable
"teacakes"
...

Evaluateendearment to "dearie"

(
(lambda
(snack)
(read‑string
(concat"Would you like ""teacakes"", ""dearie""? ")
)
)
"teacakes")
endearment
variable
"dearie"
snack
variable
"teacakes"
...

Evaluate"? " to "? "

(
(lambda
(snack)
(read‑string
(concat"Would you like ""teacakes"", ""dearie""? ")
)
)
"teacakes")
endearment
variable
"dearie"
snack
variable
"teacakes"
...

Evaluate the list to "Would you like teacakes, dearie? "

(
(lambda
(snack)
(read‑string"Would you like teacakes, dearie? ")
)
"teacakes")
endearment
variable
"dearie"
snack
variable
"teacakes"
...

Evaluate the list to "Yes, please"

(
(lambda
(snack)
"Yes, please")
"teacakes")
endearment
variable
"dearie"
snack
variable
"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
(/
(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

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)
*scratch*

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)
)
*scratch*

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)
)
*scratch*

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
(/
(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

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:

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.
...
*Help*

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
...
*Shortdoc sequence*

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")
...
*info*

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
(lambda
(colournum)
(xx‑‑insert‑line
(concat"No. "
(symbol‑namecolour)
" skeins : "
(number‑to‑stringnum)
)
)
)
coloursnum‑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
(/
(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

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
*xx*