It has been 45 days since I am an Emacs user, which implies going through some Elisp, the Lisp family language for Emacs. After modifying some modes and writing my own functions, it was time to become conscious about how Elisp works, its special forms, and syntax.
After reading “An Introduction to Programming in Emacs Lisp” almost entirely, I have decided to write a summary for myself and share it, since it could help others in the same situation: people with some programming background who wants a brief introduction to Elisp. Some references are from the Elisp Manual.
The structure follows:
- LISt Processing
- Symbols, symbol expression and variables
- Evaluating
- Defining functions (defun) and the single quote
- Local scope (let)
- Conditionals: if, then, else, cond and when
- Manipulating lists: car, cdr and cons
- Progn, lambda and condition case
- Loops: while, dolist and dotimes
- Recursion
- Debug
- Customization
1. List Processing
LISP stands for List Processing. And that’s a really meaningful name! Lists are the base of LISP, and it basically passes by lists, a structure bounded by “()”, and process/evaluate them. Indentation doesn’t matter. Just embrace the lists by “()” and keep white spaces between its members.
;; List of numbers
(1 2 3 4)
;; List inside list
'(this list has (a list inside of it))
;; List of strings
("John" "Ringo" "Paul" "George")
;; List of symbols
(john ringo paul george)
;; Mixed List
("John" 1 (2 3 "Paul") ("Ringo" george))
2. Atoms, symbols, symbol expression and variables
In Elisp, we call anything we can’t divide as an atom. The atoms are: numbers (1, 2, 3…), “symbols” (+, -, john, ringo…), and strings (“John”, “Ringo”…). Usually, we care about symbols and numbers.
The representation of symbols and lists are called symbolic expressions, s-expressions or, even shorter, sexp. The word expressions refers to the printable or the internal representation of atoms and lists.
You can use setq and defvar to define a variable. defvar differs from setq because it only sets a value if the variable doesn’t exist yet and contains a documentation string.
;; Syntax
(setq symbol &optinal value)
;; Example
(setq x 1)
;; Syntax
(defvar symbol &optinal value docstring)
;; Example
(defvar x 1 "value x")
3. Evaluating
From my point of view, the beauty in Elisp comes from this general list processing. Everything is evaluated, and the default behavior is to treat a list as a function call unless you explicitly advise with a quote that it should be seen as “plain”. Elisp interprets the list (function_name arg1 arg2)
as a call to a function named function_name
, while the other elements are the arguments to that function. You can evaluate symbols expressions with the bind C-x C-e
with the cursor at the end of the s-exp.
The evaluation itself happens too in a general form and only differs for the special forms, like defun and others we will present.
("John" "Ringo" "Paul" "George")
=> Invalid function "John"
(concat "John" "Yoko")
=> "JohnYoko"
'("John" "Yoko")
=> ("John" "Yoko")
This approach is part of the Functional Programming paradigm. Though Elisp isn’t a purely functional language, many paradigm aspects are present. You can see further information about it in its wiki.
4. Defining functions (defun)
We have seen that lists are evaluated as a function; they call and return a value. To define a function, we use the special form defun. It’s followed by the function name and its arguments. The arguments are private, so it does not change anything outside the function. Any other symbol called inside a function will bring its value from outside since Elisp has global scope. Then, we write a documentation string explaining the function behavior. The last evaluation inside defun
is the value it’ll return when called.
(defun number-plus-five (number)
"Sum argument number and five"
(+ number 5))
(number-plus-five 10)
=> 15
Though a function always returns a value, sometimes we’re more interested in its side-effects, that is, everything that is done while the function is evaluated but is not reflected in its returned value. Many things can be done as side-effects, like changing buffers, displaying a message, and so on.
5. Local scope - the “let” special form
Since the scope in Elisp is global, we need to inform when we want it to be local. It’s achieved by the special form let, which follows the structure:
(let varlist body)
Varlist is a list of variables bounded to informed values, or nil if anything is passed.
(let ((name "John") (income 3000) address)
body...)
In such a case, the variables name, income, and address will be bounded to “John”, 3000, and nil, respectively. It doesn’t matter if variables with such names appear in any other place, we guarantee its values inside the let, and we don’t propagate it to the outside.
The let form returns the last evaluated expression in its body.
Where and why it should be used: inside functions, then the global scope won’t be messed, and you guarantee the variables attend what you want just where you want it. The other variables that work in a local scope are those passed as function arguments. Anything called inside let (like functions) is evaluated at its scope. If you have a setq inside a function (defun’s body), you should isolate it inside this function scope using let.
There’s yet another way to set a variable in a local scope. In fact, it’s just the function arguments but passed interactively:
(defun myfunc ()
(interactive "p")
body...)
The “p” here stands for “prefix”. We can pass an argument like C-u [argument] M-x [myfunc]
. The “p” makes Emacs pass the prefix argument (from C-u) as the function argument. We can even build the arguments list. You can prompt the user for some of them, like in append-to-buffer built-in function:
(interactive
(list (read-buffer
"Append to buffer: "
(other-buffer (current-buffer) t))
(region-beginning)
(region-end)))
The let* special form is just like the let, except that the varlist is set in a sequence, so you can use earlier variables in the var list to set values of the following ones.
6. Conditionals: if, when, unless and cond
These are the special forms to evaluate predicates and execute a function depending on the evaluation. The syntax is enough to understand it.
(if (predicate) ; if-part
(body)) ; then-part
(if (predicate)
(then action)
(else action))
(if (> 10 5)
(message "10 > 5")
(message "10 <= 5"))
;; Like if, but without else part and support many then-actions
(when (predicate) (action1) ... )
(when (> 10 5)
(message "10 > 5")
(sleep-for 0.5)
(message "What were you expecting?"))
;; Like if, but without the then-part. Skip only when the predicate is true
(unless (predicate) (action1) ...)
; The cond form supports an arbitrary number of predicates and actions
(setq number 5)
(cond ((< number 1) (message "Number < 1"))
((< number 3) (message "Number < 3"))
((< number 5) (message "Number < 5"))
(t (message "Number >= 5"))
)
7. Manipulating lists: car, cdr and cons
The car, cdr and cons are fundamental functions to work with lists. Car returns the head of a list, its first element. Cdr points to the second term of a list, and that will return the part from the list that follows the head (if you’re not used to linked lists, read “How lists are implemented” chapter).
Car is an acronym from the phrase “Content of the address part of the register” and cdr comes from “Contests of the decremented part of the register”.
(car '(john ringo paul george))
=> john
(car (cdr '(john ringo paul george)))
=> ringo
(car (cdr '((john ringo paul) george)))
=> george
Cons is used to construct lists, like:
(cons 'john '(ringo paul george))
=> (john ringo paul george)
(cons 'beatles ())
=> (beatles)
All three are not destructive! They do not change or exclude original lists.
To find lists length:
(length '(john ringo))
=> 2
Instead of using cdr repeatedly, we can use nthcdr and pass the repeat number of times as an argument:
(car (nthcdr 2 '(john ringo paul george)))
=> paul
And instead of calling a car after an nthcdr, we can just use nth:
(nth 2 '(john ringo paul george))
=> paul
If we want to set values from a list, we need to use setcar and setcdr. This actually changes the original list.
(setq beatles '(john ringo paul george))
(setcar beatles 'david)
=> (david ringo paul george)
(setq beatles '(john ringo paul george))
(setcdr beatles '(yoko))
=> (john yoko)
9. Loops: while, dolist and dotimes
Since it’s a widespread structure from any programming language, it’s a brief syntax summary. A classical thing to do is loop through a list:
(while test-whether-list-is-empty
body...
set-list-to-cdr-of-list)
(setq beatles '(john ringo paul george))
(while beatles
(print (car beatles))
(setq beatles (cdr beatles)))
The “test-whether-list-is-empty” test can be the list itself since it returns nil if empty. Another common task is to do something a certain number of times:
(while (< count desired-number-of-times)
body...
(setq count (1+ count)))
The 1+ is like the “++” shortcut from some languages, an easy way to write (+ variable 1)
.
The other two forms, dolist and dotimes are macros. Macros are one step ahead of special forms, a syntax sugar for some common tasks. Dolist automatically shorten the list, binds the *8car** of each shortened version of the list to the first of its arguments.
(dolist (element list ...)
body)
(setq beatles '(john ringo paul george))
(dolist (element beatles)
(print element))
Dotimes automatically passes iteration number for each repetition:
(let (value)
(dotimes (number 3 value)
(setq value (cons number value))))
=> (2 1 0)
It returns value
, whether an atom or a list.
10. Progn, lambda and condition case
The progn evaluates each of its arguments and returns the value of the last one. Where and why: since progn is a single expression and can produce side effects and return a single value, it can be used as an argument to other functions, or wherever we can have only one expression, but we want to do more than one thing.
;; Syntax
(progn
body...)
;; Example
(defun zap-to-char (arg char)
"Kill up to and including ARG'th occurrence of CHAR.
Case is ignored if `case-fold-search' is non-nil in the current buffer.
Goes backward if ARG is negative; error if CHAR not found."
(interactive "p\ncZap to char: ")
(if (char-table-p translation-table-for-input)
(setq char (or (aref translation-table-for-input char) char)))
(kill-region (point) (progn
(search-forward (char-to-string char)
nil nil arg)
(point))))
We need to pass two points in the (kill-region ...)
call. At the second argument, we want to make more before passing it, so we use progn, make a search-forward, and then use (point)
.
The lambda define a lambda expression. It is used as in other languages: to write anonymous functions. You can just write a function on the fly without naming it using the defun form. It’s helpful to use with mapcar, since one of its arguments is a function. You can just write it inside mapcar using lambda.
;; Syntax
(lambda (arg-variables…)
[documentation-string]
[interactive-declaration]
body-forms…)
;; Simple example
(lambda (x)
"Return 10 times a number"
(* 10 x))
;; Example
(setq numbers '(1 2 3))
(mapcar
(lambda (x)
(* 10 x))
numbers)
=> (10 20 30)
condition-case is used to handle errors and complicated situations in which we couldn’t just stop the program.
;; Syntax
(condition-case
var
bodyform
error-handler)
10. Recursion
There’s nothing different about recursion in Elisp as you may be used to.
(defun name-of-recursive-function (argument-list)
(if stop-condition
body...) ; Base case
(name-of-recursive-function next-step-expression))
Being next-step-expression
something like (cdr list)
or a regressive counter (1- number)
and so on.
The special form cond
fits a recursive function body nicely.
There are some patterns to recursion over lists: every, accumulate and keep. With “every” we iterate over all elements and act, building (usually) another list with the result, like:
(cons act-on-car (recursive-function (cdr list)))
In accumulative, the result is a combination of every call result:
(+ (car list) (recursive-function (cdr list)))
With “keep”, we act only on elements of interest, do a test to check them.
The book’s recursion chapter is short but way more informative than this little summary: book..
11. Debug
Emacs has two debuggers: debug (internal) and edebug (requires instrument a function). M-x debug-on-entry RET my_function RET
. Then evaluate (my_func)
. Type d to evaluate next expression. To cancel: M-x cancel-debug-on-entry RET my_function RET
. The debug-on-quit
triggers with C-g
(cancel), so it’s useful to use when you’re entering infinite loops.
To use edebug call M-x edebug-defun RET
with the cursor within or just after a function definition. Then use SPC
to pass the evaluation.
(defun my-func (x)
(* 10 x)
(* 5 x)) ;; Call edebug-defun with cursor here
(my-func 1) ;; Execute the function and pass each evaluation using SPC
12. Customization
“You don’t have to like Emacs to like it.”
Emacs is easily customized, and Elisp has forms to help with it. The customization of Emacs via the .emacs
is not the purpose here.
The defcustom let the user specify a variable value. Though you can use setq, this special form offers help to customization since you can define:
- :type kind of data that variable should be set;
- :options suggest a list of values to that variable, not exclusive, only convenient;
- :group in which group the var is located, where to find it, from where does it belong;
M-x customize
let you search for groups.