Emacs Lisp dojo
A dojo is a meetup format that involves coding on projects or exercises, usually in groups, for the purpose of practice and learning. There are many dojo meetups that run for different audiences. Attendees may be familiar with the London Clojure Dojo.
Our format is:
- Everyone suggests ideas for a project.
- Break into small groups, each focusing on a project (or on the exercises below).
- Work for 90 minutes.
- Present some learnings, or maybe head straight to a pub.
Interactive exercises
These are some exercises to help learn and explore Emacs and Elisp. The aim is
to fill in the functions labelled __
, so that all the Pass:
messages show t
instead of nil
.
They are inspired by the interactive Clojure exercises on 4Clojure.
Alternatively, you may want to explore the open-ended project ideas at the bottom of this page.
Working with this org-mode file
This web page is written as an org-mode file, which means you can get it on
github, open it in Emacs, and execute the code snippets by moving to a code
block and pressing C-c C-c
. You can also open and edit the code block in
emacs-lisp mode by pressing C-c '
.
Resources to help
If you're new to Elisp, the resources below can act as starting points or references.
- Emacs itself: there are some very useful features to help you learn about
the language, available functions, current values of your variables, and
more. These are all accessible via
C-h
. You can start reading about them in the manual. - The Emacs Lisp reference manual is very comprehensive if you know what you
want to read about. This can also be read within Emacs by using
C-h i m Elisp RET
. - Steve Yegge wrote a brief overview of all the language features. It's quite old now but is still a nice place to start.
- Xah Lee's practical Emacs Lisp guide may be easier to digest than the full manual, and covers a lot of topics.
Contributing
Changes and new exercises are very welcome! Please submit PRs on github.
Index
Strings - uppercase
Transform the given string into uppercase:
(defun __ (s)) (let* ((result (__ "helloworld")) (pass (string= result "HELLOWORLD"))) (format "Pass:%S\n%S" pass result))
Penultimate element
Return the second last element in a list. If there is only one item in the list, return nil.
(defun __ (li) nil) (let* ((result1 (__ '(1 2 3 4 5))) (pass1 (equal result1 4)) (result2 (__ '("a" "b" "c"))) (pass2 (equal result2 "b")) (result3 (__ '([1 2] [3 4]))) (pass3 (equal result3 [1 2])) (result4 (__ '(1))) (pass4 (equal result4 nil))) (format "Pass:%S\n%S\n\nPass:%S:\n%S\n\nPass:%S\n%S\n\nPass:%S\n%S" pass1 result1 pass2 result2 pass3 result3 pass4 result4))
(Ported from 4Clojure #20)
Fizzbuzz
Return a list with the results of fizzbuzz for a range of 1-100. This means:
- If the number is divisible by 3, the value is "Fizz".
- If the number is divisible by 5, the value is "Buzz".
- If the number is divisible by 3 and 5, the value is "FizzBuzz".
(defun __ () nil) (let* ((expected '(1 2 "Fizz" 4 "Buzz" "Fizz" 7 8 "Fizz" "Buzz" 11 "Fizz" 13 14 "FizzBuzz" 16 17 "Fizz" 19 "Buzz" "Fizz" 22 23 "Fizz" "Buzz" 26 "Fizz" 28 29 "FizzBuzz" 31 32 "Fizz" 34 "Buzz" "Fizz" 37 38 "Fizz" "Buzz" 41 "Fizz" 43 44 "FizzBuzz" 46 47 "Fizz" 49 "Buzz" "Fizz" 52 53 "Fizz" "Buzz" 56 "Fizz" 58 59 "FizzBuzz" 61 62 "Fizz" 64 "Buzz" "Fizz" 67 68 "Fizz" "Buzz" 71 "Fizz" 73 74 "FizzBuzz" 76 77 "Fizz" 79 "Buzz" "Fizz" 82 83 "Fizz" "Buzz" 86 "Fizz" 88 89 "FizzBuzz" 91 92 "Fizz" 94 "Buzz" "Fizz" 97 98 "Fizz" "Buzz" )) (result (__)) (pass (equal result expected))) (format "Pass:%S\n%S" pass result))
Flatten an arbitrarily nested list
Pull out all the nested elements into a single list:
(defun __ (a &rest b)) (let* ((result1 (__ '(1 2 3))) (pass1 (equal result1 '(1 2 3))) (result2 (__ '(1 2 (3 4) (5 (6 (7)))))) (pass2 (equal result2 '(1 2 3 4 5 6 7))) (result3 (__ '("a" "b") '(c nil d) '(1 (2 3 (4 5))))) (pass3 (equal result3 '("a" "b" c d 1 2 3 4 5)))) (format "Pass:%S\n%S\nPass:%S\n%S\nPass:%S\n%S" pass1 result1 pass2 result2 pass3 result3))
Convert a list into a hash table
Given a list of cons cells (eg. '((a . 123) (b . 456))
) create a hash table
where the car is the key and the cdr is the value.
(defun __ (s)) (let* ((result (__ '((a . 123) (b . 456) (c . "789")))) (pass (equal result #s(hash-table size 3 data (a 123 b 456 c "789"))))) (format "Pass:%S\n%S" pass result))
Count the lines and words in a buffer
Depending on whether the second argument is the symbol 'lines
or 'words
, you
should return a number that represents the appropriate count. Bonus points if
you don't use any of the default line/word count functions.
(defun __ (buffer arg) 0) (with-temp-buffer (insert "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Maecenas sed enim ut sem viverra aliquet. In mollis nunc sed id semper risus in. In hac habitasse platea dictumst quisque sagittis. Facilisis magna etiam tempor orci eu. Tempus egestas sed sed risus pretium quam vulputate dignissim. Eros donec ac odio tempor orci dapibus ultrices. Nam libero justo laoreet sit. Quis imperdiet massa tincidunt nunc pulvinar. Quis imperdiet massa tincidunt nunc pulvinar sapien et. Consequat semper viverra nam libero justo. Volutpat maecenas volutpat blandit aliquam etiam erat. Viverra tellus in hac habitasse platea. Condimentum vitae sapien pellentesque habitant. Eleifend mi in nulla posuere sollicitudin aliquam ultrices sagittis orci. Vitae elementum curabitur vitae nunc sed velit dignissim sodales. Vel risus commodo viverra maecenas. Tellus molestie nunc non blandit massa enim. Sed ullamcorper morbi tincidunt ornare massa eget egestas purus viverra. Donec ultrices tincidunt arcu non sodales neque sodales ut. Eu tincidunt tortor aliquam nulla. Amet justo donec enim diam vulputate. Tristique senectus et netus et malesuada fames. Tellus elementum sagittis vitae et. Blandit cursus risus at ultrices mi tempus. Sit amet purus gravida quis blandit turpis. Metus vulputate eu scelerisque felis imperdiet. Nulla porttitor massa id neque. Dictum fusce ut placerat orci nulla pellentesque. Pulvinar mattis nunc sed blandit libero volutpat sed. Amet venenatis urna cursus eget nunc scelerisque viverra mauris in. Morbi quis commodo odio aenean. Pellentesque massa placerat duis ultricies. Tristique sollicitudin nibh sit amet. Gravida cum sociis natoque penatibus et magnis dis parturient. Ut ornare lectus sit amet est. Enim nunc faucibus a pellentesque sit amet porttitor. Nisl suscipit adipiscing bibendum est ultricies integer quis. Risus pretium quam vulputate dignissim suspendisse in.") (let* ((result1 (__ (current-buffer) 'lines)) (pass1 (= result1 21)) (result2 (__ (current-buffer) 'words)) (pass2 (= result2 283))) (format "Pass:%S\n%S\nPass:%S\n%S" pass1 result1 pass2 result2)))
Reimplement mapcar
map is a function that applies a given function to each element of a given
list. In Emacs there are a few variants of map functions. Write a function that
mimics the behaviour of mapcar
. It accepts two argument: the function to apply,
and the list of args to operate on.
(defun __ (fn args)) (let* ((result1 (__ 'upcase '("apple" "banana" "pear"))) (pass1 (equal result1 '("APPLE" "BANANA" "PEAR"))) (result2 (__ (lambda (arg) (= 0 (mod arg 2))) '(0 1 2 3 4 5 6 7 8 9))) (pass2 (equal result2 '(t nil t nil t nil t nil t nil)))) (format "Pass:%S\n%S\nPass:%S\n%S" pass1 result1 pass2 result2))
Handle errors
You can find some information on error handling in the manual, along with a list
of standard errors. Write a function that catches some particular errors and
returns their value. Hint: you probably want to use (condition-case)
.
(defun __ (fn)) (let* ((result1 (__ (lambda () (error "Something went wrong: %s" 'mysymbol)))) (pass1 (equal result1 "Something went wrong: mysymbol")) (result2 (__ (lambda () (/ 5 0)))) (pass2 (equal result2 "Division by zero")) (result3 (__ (lambda () (throw 'catchmeplease "myvalue")))) (pass3 (equal result3 '(uncaught . "myvalue")))) (format "Pass:%S\n%S\nPass:%S\n%S\nPass:%S\n%S" pass1 result1 pass2 result2 pass3 result3))
Throw and catch
catch
and throw
are used for control flow, but not necessarily errors. Write a
function that executes the given fn
, catches the throw
from our lambda, and
returns the expected value:
(defun __ (fn)) (let* ((result (__ (lambda () (throw 'label "message")))) (pass (equal result '(label . "message")))) (format "Pass:%S\n%S" pass result))
Use a regex to extract numbers from a buffer
The numbers should be extracted into a list. Note: \d
is not supported.
(defun __ (buffer)) (let* ((result1 (with-temp-buffer (insert "There are 2 numbers in this buffer. The second one is 123") (__ (current-buffer)))) (pass1 (equal result1 '(2 123))) (result2 (with-temp-buffer (insert "There are no numbers in this buffer") (__ (current-buffer)))) (pass2 (equal result2 nil))) (format "Pass:%S\n%S\nPass:%S\n%S" pass1 result1 pass2 result2))
Make an HTTP request and parse the JSON response
Make an HTTP request to httpbin.org and convert the "args" from the JSON response into an alist.
(defun __ (url) nil) (let* ((result (__ "https://httpbin.org/get?one=two&three=four")) (pass (equal result '((one . "two") (three . "four"))))) (format "Pass:%S\n%S" pass result))
Read output from a shell command
There are various ways to call external processes in Emacs. Eval the given argument in a shell and return the result.
(defun __ (shellcommand)) (let* ((result (__ "echo 'always eval user input'")) (pass (equal result "always eval user input"))) (format "Pass:%S\n%S" pass result))
Parse data from an org buffer
Given the string below, convert it to an org-mode buffer, parse out the headline
and tags, and return a list where each item looks like ("HEADLINE" . '("TAG1" "TAG2"))
.
(defun __ (s) nil) (let* ((org-string "* Headline one :foo: * Headline two :foo:bar: * Headline three :bar:") (result (__ org-string)) (pass (equal result '(("Headline one" . ("foo")) ("Headline two" . ("foo" "bar")) ("Headline three" . ("bar")))))) (format "Pass:%S\n%S" pass result))
Apply a custom font face to TODO words in a buffer
Each "TODO" word in the buffer below should have my-todo-face
applied. You might
find it easier to open a test buffer where you can visually check the faces.
When you're done, try adjusting the exercise to support and test multiple faces, or extend the face to apply to the rest of the line matching TODO.
The fic-mode codebase may help you: it's a minor-mode that essentially just highlights some user-defined keywords.
(defface emacs-london/my-todo-face '((t (:inherit 'warning))) "") (defun emacs-london/check-faces (buffer) "This returns a list where each item is t if the TODO font matches my-todo-face, else nil" (with-current-buffer buffer (save-excursion (goto-char (point-min)) (cl-loop until (not (search-forward-regexp "TODO" nil t)) collect (equal (face-at-point t) 'emacs-london/my-todo-face))))) (defun __ (buffer)) (with-temp-buffer (insert "import math # TODO: remove unused import def main(arg): helloworld = 'helloworld' # TODO: inline this? return helloworld # TODO: execute main()?") (let* ((result (progn (__ (current-buffer)) (emacs-london/check-faces (current-buffer)))) (pass (equal result '(t t t) ))) (format "Pass:%S\n%S" pass result)))
Anagram finder
Write a function which finds all the anagrams in a given vector of words. Your function should return a list of lists, where each sub-list is a group of words which are anagrams of each other. Words without any anagrams should not be included in the result.
(defun __ (v) nil) (let* ((result (__ ["meat" "mat" "team" "mate" "eat"])) (pass (equal result '(("meat" "team" "mate")))) (result2 (__ ["veer" "lake" "item" "kale" "mite" "ever"])) (pass2 (equal result '(("veer" "ever") ("lake" "kale") ("mite item"))))) (format "Pass:%S\n%S\n\nPass:%S\n%S" pass result pass2 result2))
(Ported from 4Clojure #77).
Analyse a tic tac toe board
A tic-tac-toe board is represented by a two dimensional vector. X is represented
by 'x
, O is represented by 'o
, and empty is represented by 'e
. A player wins by
placing three Xs or three Os in a horizontal, vertical, or diagonal row. Write a
function which analyses a tic-tac-toe board and returns 'x
if X has won, 'o
if O
has won, and nil
if neither player has won.
(defun __ (board) nil) (let* ((result1 (__ [[e e e] [e e e] [e e e]])) (pass1 (equal nil result1)) (result2 (__ [[x e o] [x e e] [x e o]])) (pass2 (equal 'x result2)) (result3 (__ [[e x e] [o o o] [x e x]])) (pass3 (equal 'o result3)) (result4 (__ [[x e o] [x x e] [o x o]])) (pass4 (equal nil result4)) (result5 (__ [[x e o] [o o e] [o e x]])) (pass5 (equal 'o result5))) (format "Pass:%S\n%S\nPass:%S\n%S\nPass:%S\n%S\nPass:%S\n%S\nPass:%S\n%S\n" pass1 result1 pass2 result2 pass3 result3 pass4 result4 pass5 result5))
(Ported from http://www.4clojure.com/problem/73).
Advice: 2 + 2 = 5
The advice feature allows you to decorate other functions. This means you can patch existing code to change its behaviour.
The first time your function is called, it should advise the +
function so that
(+ 2 2)
returns 5. The second time it's called, it should remove the advise, so
that subsequent calls to +
return 4 again.
Be careful with this. Breaking (+)
could have unintended consequences!
(defun __ () nil) (let* ((result1 (+ 2 2)) (pass1 (equal result1 4)) (result2 (progn (__) (+ 2 2))) (pass2 (equal result2 5)) (result3 (progn (__) (+ 2 2))) (pass3 (equal result3 4))) (format "Pass:%S\n%S\n\nPass:%S:\n%S\n\nPass:%S\n%S" pass1 result1 pass2 result2 pass3 result3))
Open-ended project ideas
Some ideas to get the conversation started:
- Write a syntax highlighter for a language of your choice.
- Build a fuzzy browser search interface to replace Spotlight/Alfred. You can use Alvaro's excellent post as a starting point.
- Write a test runner that can run tests and display their output.
- Write your own modeline.
See the projects page for more ideas.