For more advanced operations in the One True Editor, it is worth learning at least a little Lisp — specifically Emacs Lisp, usually shortened to Elisp.
It is the primary language used for Emacs extensions, packages, and configuration.
A remarkable number of recipes found on the Internet boil down to:
Paste the following lines into this particular file somewhere in your home directory and reload Emacs.
And that is fine.
You can survive for years that way.
Still, it may be useful to go a little deeper and understand at least what we are actually pasting, where, and why.
Besides, if we are already dismantling the King of Editors piece by piece, completely ignoring Lisp would be rather difficult.
Anyone firmly committed to the “just tell me what to paste and where” school of thought may, of course, skip the following section.
Or rather: wall of text.
A World That Will Never Return
Long ago, programming languages had simple, readable syntax, and you could tell what a program did merely by looking at it from across the room.
For example:
.section .data
msg:
.ascii "Hello, Cruel World!\n"
msg_len = . - msg
.section .text
.globl _start
_start:
movl $4, %eax
movl $1, %ebx
movl $msg, %ecx
movl $msg_len, %edx
int $0x80
movl $1, %eax
xorl %ebx, %ebx
int $0x80
Simple.
Readable.
Self-explanatory.
Right?
Things could have stayed that way, but a rather large number of high-level languages appear to disagree.
The history of Lisp inside Emacs is quite interesting.
The original Emacs was not written in Lisp. It began life as a collection of macros for TECO.
Later, Bernie Greenberg wrote a version of Emacs for Multics in MacLisp. It demonstrated that using an actual programming language as the extension language for an editor was considerably more pleasant than endlessly expanding a macro system.
Richard Stallman later recalled an interesting side effect.
Secretaries in Greenberg’s office started writing extensions for Emacs themselves.
They had been given a manual explaining how to extend the editor, but the manual never told them that what they were doing was programming.
So, unaware that they supposedly could not program, they read the documentation…
…and learned to program.
Beautiful.
Lisp then remained with Emacs for the following decades.
We may as well get acquainted.
Language Basics
Let us begin at the beginning.
And since every programming language must begin by greeting this cruel world, there is no reason to break with tradition:
(defun hello-world ()
"A function that greets the world."
(interactive)
(message "Hello, cruel world"))
The first thing you notice?
Parentheses.
Lots of parentheses.
We will get used to them.
What do we have here?
defun— a function definition,hello-world— its name,()— the argument list, empty in this case,"A function that greets the world."— the function’s documentation,(interactive)— a declaration that the function may be invoked as an Emacs command,(message ...)— the actual body of the function.
Running It
Paste the code into the scratch buffer.
It is an excellent place for first experiments with Elisp.
Put point after the final parenthesis of the definition and press:
C-x C-e
which invokes:
M-x eval-last-sexp
Emacs evaluates the preceding Lisp expression — an S-expression, usually shortened to sexp — and the function becomes available for the current session.
Now execute:
M-x hello-world
and the echo area displays:
Hello, cruel world
Point matters when using
C-x C-e. The command evaluates the expression immediately before it. Put point somewhere in the middle of the definition and you may evaluate a completely different fragment — or get an error.
What about that string immediately following the argument list?
It is the function’s docstring.
We can now run:
M-x describe-function RET hello-world RET
and Emacs will display documentation for our brand-new function.
Arguments and interactive
How about an argument?
(defun hello-someone (name)
"Ask for a name and display a greeting."
(interactive "sEnter your name: ")
(message "Hello, %s. Lisp isn't that bad!" name))
This function accepts one argument: name.
interactive tells Emacs how to obtain that argument when the function is invoked interactively.
The letter:
s
means a string.
So after:
M-x hello-someone
Emacs asks:
Enter your name:
and the entered text becomes the value of name.
Two arguments?
Certainly:
(defun hello-someone (name age)
"Ask for a name and age, then display a greeting."
(interactive "sEnter your name: \nnAnd your age: ")
(message "Hello, %s (%d). Lisp isn't that bad!" name age))
Here:
s
means string, while:
n
means number.
If we enter something that is not a number, Emacs keeps asking.
We may, naturally, surrender:
C-g
A universal solution.
Some Arithmetic
Let us try something slightly more useful:
(defun multiply (a b)
"Ask for two numbers and insert their product into the current buffer."
(interactive "nEnter the first number: \nnEnter the second number: ")
(let ((result (* a b)))
(insert (format "%d" result))))
Here we encounter one of Lisp’s most distinctive features:
(* a b)
The operator appears before its operands.
This is known as prefix notation, or — appropriately enough — Polish notation.
Not Reverse Polish Notation.
That would look more like:
a b *
The Polish edition of this article may therefore indulge in a small amount of national pride.
Without reversing anything.
The code also does two other interesting things.
First, it creates a local binding:
(let ((result (* a b)))
...)
Then, instead of message, it uses:
insert
which inserts text directly into the current buffer at point.
Data Types
Simple Variables
Variables may have global values:
(setq my-variable 42)
(setq my-string "This is my string")
(setq my-boolean t)
setq assigns a value to a symbol.
We can also establish a local binding with let:
(let ((my-variable 42))
(message "Value: %d" my-variable))
The value 42 applies only inside the body of this let.
Boolean values are:
t
for true, and:
nil
for false.
nil is also the empty list.
This is not a coincidence.
This is Lisp.
Lists
Lists are one of Lisp’s fundamental data structures.
We can create one like this:
(setq my-list '(1 2 3 4 5))
The apostrophe means:
Do not evaluate this. Treat it as data.
Without it, Lisp would try to interpret:
(1 2 3 4 5)
as a call to something named 1 with the arguments 2, 3, 4, and 5.
We may also construct a list explicitly:
(setq my-list (list 1 2 3 4 5))
The classic list functions include:
car— the first element,cdr— everything except the first element,nth— the element at a particular index.
For example:
(car '(a b c)) ; => a
(cdr '(a b c)) ; => (b c)
(nth 2 '(a b c d)) ; => c
Indices, naturally, begin at zero.
As civilization intended.
Modifying Lists
setf can assign a value to a location described by another expression:
(setq my-list '(1 2 3 4 5))
(setf (nth 1 my-list) 70)
(setf (nth 2 my-list) "who knows")
Now:
my-list
contains:
(1 70 "who knows" 4 5)
An element can easily be added to the front:
(push 0 my-list)
And append joins sequences:
(append my-list '(6 7))
There is an important difference: append returns a new list. It does not automatically store it back in my-list.
To change the variable:
(setq my-list (append my-list '(6 7)))
Naturally:
(append '(a b) '(c d))
returns:
(a b c d)
Association Lists
An alist, or association list, is a list of key-value pairs.
For example:
(setq my-dictionary
'((number . 42)
(identifier . "some string")))
It can be thought of as a very simple dictionary.
For example:
(alist-get 'number my-dictionary)
returns:
42
Alists appear in Emacs configuration remarkably often.
Much more often than you may initially wish.
Eventually it stops bothering you.
Vectors
A vector is a fixed-length sequence:
(setq my-vector [10 20 30])
An element can be retrieved with:
(aref my-vector 1)
which returns:
20
Unlike lists, vectors provide direct indexed access to their elements.
Conditionals
if
The basic conditional looks like this:
(if condition
when-true
when-false)
For example:
(if (> variable 42)
(message "The value is too large!")
(message "Still within limits."))
Once again, we see prefix notation:
(> variable 42)
If the then branch needs to perform several forms, they can be grouped with:
(progn
(do-one-thing)
(do-another)
(do-a-third))
For example:
(if (> variable 42)
(progn
(message "Too much!")
(setq variable 42))
(message "Fine."))
when and unless
When no “otherwise” branch is needed, the code can often be made clearer.
when:
(when (some-condition)
(message "Condition satisfied!"))
and unless:
(unless (some-condition)
(message "Condition not satisfied. I can fix that!"))
when roughly means:
execute the body if the condition is true,
while unless means:
execute the body if the condition is false.
cond
cond evaluates a sequence of conditions.
It resembles a more flexible if / else if chain and can sometimes fill a role similar to switch in other languages.
Each branch may contain an arbitrary condition:
(defun check-number (x)
(interactive "nEnter a number: ")
(cond
((< x 0)
(message "The number is negative."))
((= x 0)
(message "The number is exactly zero."))
((> x 10)
(message "The number is greater than 10."))
(t
(message "The number is positive, but no greater than 10."))))
The final:
(t ...)
acts like a final else.
Combining Conditions
Conditions can be combined with:
and
or
not
For example:
(and (> x 0) (< x 10))
or:
(not (= x 42))
Loops
while
The basic loop is:
(while condition
...)
For example:
(let ((counter 3))
(while (> counter 0)
(insert (format "Countdown: %d...\n" counter))
(setq counter (- counter 1))))
dolist
dolist iterates over the elements of a list:
(let ((text-editors
'("Emacs" "And" "Then" "Nothing" "For" "A While")))
(dolist (editor text-editors)
(insert (format "* %s\n" editor))))
The ranking is, naturally, entirely objective.
dotimes
dotimes executes its body a specified number of times and provides a counter beginning at zero:
(dotimes (i 3)
(insert (format "This is repetition number: %d\n" i)))
The result:
This is repetition number: 0
This is repetition number: 1
This is repetition number: 2
Technically, dotimes is a macro.
Let us not worry yet about why it is a macro.
There will be time for that.
Functions and Arguments
Required Arguments
Arguments appearing before special markers such as &optional and &rest are required.
For:
(defun add (a b)
(+ a b))
both arguments must be supplied:
(add 10 20)
Optional Arguments
Optional arguments follow &optional:
(defun greeting (name &optional title)
(if title
(message "Good morning, %s %s!" title name)
(message "Hello, %s!" name)))
We can now call:
(greeting "John")
or:
(greeting "John" "Mr.")
If title is omitted, its value is nil.
So:
(if title ...)
neatly distinguishes the two cases.
Any Number of Arguments
&rest means:
collect all remaining arguments into a list.
For example:
(defun sum-everything (first &rest rest)
"Add FIRST to all remaining numbers."
(let ((sum first))
(dolist (number rest)
(setq sum (+ sum number)))
(message "Sum: %d" sum)))
Calling:
(sum-everything 10 1 2 3)
makes:
rest
contain:
(1 2 3)
Interactive Calls
We already know that interactive allows a function to become an Emacs command.
It can also specify where its arguments should come from.
A few particularly useful codes are:
s text entered by the user
n number
f name of an existing file
F file name; the file need not exist yet
b name of an existing buffer
B buffer name; the buffer need not exist yet
r beginning and end of the region as two numeric arguments
This is nowhere near the complete list.
It is long enough to write some genuinely useful commands already.
And short enough not to regret learning Lisp just yet.
Configuring Emacs
All right.
What was all this knowledge for?
To stop blindly copying:
(setq ...)
(add-hook ...)
(require ...)
from the Internet.
Or at least to copy it with slightly greater awareness.
Where Does the Configuration Live?
Historically, the classic file was:
~/.emacs
and Emacs still supports it.
There are, however, more convenient locations:
~/.emacs.d/init.el
and the XDG-compatible:
~/.config/emacs/init.el
Modern Emacs supports both.
There is one important trap: the traditional locations take precedence.
Emacs checks locations including:
~/.emacs.el
~/.emacs
~/.emacs.d/init.el
and if one of them exists, it may never reach:
~/.config/emacs/init.el
For this series I will use:
~/.emacs.d/init.el
It is a long-established, familiar layout and remains convenient once the configuration begins to spread across several files.
If an old:
~/.emacs
exists and the destination init.el does not, it can be moved:
mkdir -p ~/.emacs.d
mv ~/.emacs ~/.emacs.d/init.el
A Separate File for Customize
In the previous episode we met Customize.
Settings saved through it are ultimately written as Emacs Lisp code.
Your init.el may contain something like:
(custom-set-faces
;; custom-set-faces was added by Custom.
;; If you edit it by hand, you could mess it up, so be careful.
;; Your init file should contain only one such instance.
;; If there is more than one, they won't work right.
'(default
((t
(:inherit nil
:extend nil
:stipple nil
:inverse-video nil
:box nil
:strike-through nil
:overline nil
:underline nil
:slant normal
:weight regular
:height 143
:width normal
:foundry "SRC"
:family "Hack Nerd Font Mono")))))
I would rather not mix machine-generated customization with configuration written by hand.
So let us create a separate file:
~/.emacs.d/custom.el
If existing custom-set-variables or custom-set-faces forms generated by Customize already live in init.el, they can be moved there.
Then add this to init.el:
;; Keep Customize-generated settings in a separate file.
(setq custom-file
(expand-file-name "custom.el" user-emacs-directory))
;; Load it if it already exists.
(when (file-exists-p custom-file)
(load custom-file))
And now, instead of treating the code as an incantation, we can actually read it.
user-emacs-directory
The variable:
user-emacs-directory
points to the directory used for the user’s Emacs files.
With our layout, that is:
~/.emacs.d/
We can check it:
(message "%s" user-emacs-directory)
With another configuration layout, its value may be different.
expand-file-name
This expression:
(expand-file-name "custom.el" user-emacs-directory)
constructs the full path to the file inside the Emacs user directory.
We could hard-code something like:
/home/user/.emacs.d/custom.el
but then the configuration immediately becomes less portable.
It may eventually move to another computer, another account, another init directory, or even an operating system with strange opinions about path names.
Best not to assume too much.
setq custom-file
This line:
(setq custom-file ...)
tells Customize:
from now on, write your generated settings here.
load
Finally:
(load custom-file)
loads the contents of custom.el.
Telling Customize where to save the code is not enough.
On the next startup, that code still needs to be executed.
Why file-exists-p?
If the file does not exist yet:
(load custom-file)
signals an error.
So we use:
(when (file-exists-p custom-file)
(load custom-file))
In other words:
if the file exists, load it.
And we have just written our first piece of configuration that, several pages ago, would have looked like a mysterious collection of parentheses.
Progress.
Summary
These really are only the absolute basics of Emacs Lisp.
We have not even touched:
- macros,
- anonymous functions,
- hooks,
- keymaps,
- symbols and their properties,
requireandprovide,- packages,
- more advanced data structures,
- or any of the subjects that cause the number of parentheses to begin growing exponentially.
Good.
The point was simply to make those cryptic snippets pasted into the One True Editor’s configuration file a little less cryptic from now on.
When we encounter:
(when (file-exists-p custom-file)
(load custom-file))
we no longer need to take the Internet entirely on faith.
We can at least say:
Ah. I know what those parentheses are doing.
More or less.
