It’s remarkable just how many types of programming are possible with Emacs lisp. In fact, it’s hard to find a style of programming that isn’t possible. I’ve translated a few examples from other languages into elisp to showcase this diversity.
Iterating over a sequence
Suppose we want to double every item in a list. In Ruby, we might write it like this, using map:
some_list = [1, 2, 3]
some_list.map{|x| x*2 }
In Elisp, we can use mapcar
:
(setq some-list '(1 2 3))
(mapcar (lambda (x) (* x 2)) some-list)
In Python, we might use a list comprehension:
some_list = [1, 2, 3]
[x * 2 for x in some_list]
Elisp has the loop
macro (originally from Common Lisp):
(eval-when-compile (require 'cl))
(setq some-list '(1 2 3))
(loop for x in some-list collect (* x 2))
In Scala, we can use special arguments to denote the function arguments:
val someList = List(1, 2, 3)
someList.map(_ * 2)
Using anaphoric macros we can do this in elisp:
(require 'dash) ;; available on MELPA and Marmalade
(setq some-list '(1 2 3))
(--map (* it 2) some-list)
Searching a sequence
Suppose we want to find the first integer in a list that’s lower than some value. In Java, we’d probably use a for-each loop, terminating as soon as we find the value we’re looking for.
import java.util.List;
// inside some class
public Integer findLessThan(Integer threshold, List<Integer> list) {
for (Integer item: list) {
if (item < threshold) {
return item;
}
}
return null;
}
We can do this in elisp using dolist
and return
:
(eval-when-compile (require 'cl))
(defun find-less-than (threshold list)
(dolist (item list)
(when (< item threshold)
(return item))))
In Haskell, we’d write this in a functional style, composing functions:
findLessThan :: Ord c => c -> [c] -> c
findLessThan threshold = head . filter . (< threshold)
We can do this in elisp too:
(eval-when-compile (require 'cl))
(require 'dash)
(defun find-less-than (threshold list)
(->> list
(--filter (< it threshold))
first))
Function arguments
Suppose we want to find the mean of several numbers. In C, we might write:
float mean(float x, float y) {
return (x + y) / 2;
}
No sweat in elisp:
(defun mean (x y)
(/ (+ x y) 2))
However, C doesn’t support variadic functions without also passing in the number of arguments. In many other languages, we can write a function that takes any number of arguments. In JavaScript:
function mean() {
var sum = 0;
for (var i=0; i<arguments.length; i++) {
sum += arguments[i];
}
return sum / arguments.length;
}
Elisp supports variadic functions out of the box. This gives us a straightforward translation:
(defun mean (&rest args)
(let ((sum (apply '+ args)))
(/ sum (length args))))
In Python, we might use keyword arguments when calling functions. This can make function calls clearer (we can see which argument is which) and facilitates optional arguments:
def greet(greeting='Hello', name='anonymous'):
return "%s %s!" % (greeting, name)
greet() # "Hello anonymous!"
greet(greeting='Hi') # "Hi anonymous!"
greet(name='Wilfred', greeting='Hey') # "Hey Wilfred!"
We can use defun*
to do this in elisp:
(eval-when-compile (require 'cl))
(defun* greet (&key (greeting "Hello") (name "anonymous"))
(format "%s %s!" greeting name))
(greet)
(greet :greeting "Hi")
(greet :name "Wilfred" :greeting "Hey")
Destructuring and Pattern Matching
In CoffeeScript, it’s possible to destructure an array like this:
sumPair = (pair) ->
[first, second] = pair
first + second
Elisp has you covered:
(eval-when-compile (require 'cl))
(defun sum-pair (pair)
(destructuring-bind (first second) pair
(+ first second)))
In functional languages like Ocaml, we can use a more general technique of pattern matching:
let rec sum_list list = match list with
| [] -> 0
| (x::xs) -> 1 + (sum_list xs);
Elisp can do pattern matching too, with pcase
:
(defun sum-list (list)
(pcase list
(`nil 0)
;; note that elisp does not do TCO
;; but see https://github.com/Wilfred/tco.el
(`(,x . ,xs) (+ x (sum-list xs)))))
Monads
Monads, as popularised by Haskell, don’t really make sense without type classes. However, the Maybe monad has a natural elisp equivalent. An inexperienced Haskell programmer might write:
maybeAdd :: Maybe Int -> Maybe Int -> Maybe Int
maybeAdd x y =
case x of
Just x' ->
case y of
Just y' -> Just $ x' + y'
_ -> Nothing
_ -> Nothing
There’s a lot of wrapping and unwrapping here, which a monad can do for us:
maybeAdd :: Maybe Int -> Maybe Int -> Maybe Int
maybeAdd x y = do
x' <- x
y' <- y
return $ x' + y'
(An experienced Haskeller would just use liftM2 (+)
, but that’s not
relevant here.)
dash.el provides -when-let*
(equivalent to Scheme’s and-let
) which
allows us to mimic this behaviour:
(require 'dash)
(defun maybe-add (x y)
(-when-let* ((x* x)
(y* y))
(+ x* y*)))
Objects
A classic example of a class-based code structure might be a monster in a game:
class Monster(object):
def __init__(self):
self.health = 100
self.alive = True
def take_damage(damage):
self.health -= damage
if self.health =< 0:
self.alive = False
Elisp has EIEIO (Enhanced Implementation of Emacs Interpreted Objects), which is an implementation of CLOS (the Common Lisp Object System). So, we can straightforwardly translate this:
(require 'eieio)
(eval-when-compile (require 'cl))
(defclass monster ()
((health :initform 100)
(alive :initform t)))
(defmethod take-damage ((m monster) damage)
(decf (oref m health) damage)
(when (<= (oref m health) 0)
(setf (oref m alive) nil)))
No discussion of object-oriented code would be complete, of course, without an example of class-based inheritance:
class BossMonster(Monster):
def __init__(self):
super(BossMonster, self).__init__()
self.health = 500
EIEIO version:
(defclass boss-monster (monster)
((health :initform 500)))
Finally, EIEIO also has support for more exotic object-oriented features, such as mixins:
class TalksMixin(object):
catchphrase = "Rawr!"
def say(self, player):
return self.catchphrase
class NoisyMonster(Monster, TalksMixin):
pass
EIEIO:
(defclass talks-mixin ()
((catchphrase :initform "Rawr!")))
(defmethod say ((thing talks-mixin))
(oref thing catchphrase))
(defclass noisy-monster (monster talks-mixin)
())
Namespaces
Elisp has separate namespaces for functions and variables. So if we
store a function in a variable, we have to use funcall
to use
it. Scheme, however, is a lisp-1 with a single namespace. In Scheme we
can write:
;; assign a function to the symbol
(define add-two
(lambda (x) (+ x 2)))
;; assign a value to a symbol
(define two 2)
(add-two two) ;; 4
With a short macro, we can actually execute this code unchanged:
(defmacro define (name object)
`(setq ,name (fset ',name ,object)))
(define add-two
(lambda (x) (+ x 2)))
(define two 2)
(add-two two) ;; 4
In Clojure, we can use explicit namespaces to separate code. This prevents us having to worry about name clashes.
;; idiomatic clojure would use the ns macro here instead
(in-ns 'hello)
(clojure.core/defn say []
"Hello world")
(in-ns 'goodbye)
(clojure.core/defn say []
"Goodbye world")
(hello/say)
There’s a codex.el package that allows us to do this in elisp:
(require 'codex)
(defcodex hello
(:use emacs))
(in-codex hello
(emacs:defun say () "Hello world"))
(defcodex goodbye
(:use hello)
(:use emacs))
(in-codex hello
(emacs:defun say () "Goodbye world")
(hello:say))
What elisp doesn’t have
Elisp can’t do everything. There are some languages features that simply can’t be implemented by the users. There are no reader macros, there’s no FFI, and there’s no multithreading (though threads are being worked on).
There are also powerful language features that could be implemented, but haven’t yet been implemented in elisp. For example, metaclasses (implemented in CLOS), hygienic macros (implemented in Common Lisp), logic programming (implemented in Clojure) or even a type system (implemented in Scheme).
Should I write code like this?
For the features I have demonstrated, some examples are carefully
chosen to showcase the capabilities of elisp. For example, the
define
macro still won’t allow you to write ((foo) bar)
, it’s a
syntax error. Other examples are impractical (codex.el currently makes
edebug unusable), whilst still others are so rarely used that other
elisp developers will need time to understand the code.
Used in moderation however, these are all excellent tools which an elisp programmer can use. He or she can bend the language according to the problem at hand, rather than the other way round.
All that aside, elisp is an immensely flexible, deeply hackable language. Not only does it enable you to be productive extremely quickly (“learning any amount of elisp makes your life better immediately”), it also provides a whole zoo of language features, providing an elegant way of expressing virtually any program.