ECL is of course Embeddable Common Lisp, a small but quite complete Common Lisp implementation that can be easily embedded in your C applications to act as dynamic extension language, but it is also a fine stand-alone implementation. It includes an interpreter and a compiler, and can even produce compact binaries. Read this blog post by Chris Kohlhepp for a brief and practical overview.
SLIME is the Superior Lisp Interaction Mode for Emacs, a great Emacs environment within which one can write code for a number of Common Lisp implementations.
I had to consult a number of different sources to get SLIME and ECL working on OSX (El Capitan) using homebrew, so I’ve decided to put it all together in this short howto.
(This post assumes you have homebrew and Emacs installed. For Emacs on OSX, I usually just do
brew install --cocoa --srgb emacs. Thanks to user Aidenn0 on reddit for the reminder to mention the emacs install!)
I use this MacBook for development with XCode, so I would not have thought this step necessary. Without it however, your SLIME will break at startup during the ECL compile of the SWANK slime server due to Clang not being able to find the
gmp.h file. The error looks something like this:
.slime/fasl/2016-03-04/ecl-16.0.0-darwin-x86_64/packages.c:5: In file included from /usr/local/Cellar/ecl/16.0.0/include/ecl/ecl-cmp.h:22: In file included from /usr/local/Cellar/ecl/16.0.0/include/ecl/ecl.h:35: /usr/local/Cellar/ecl/16.0.0/include/ecl/config.h:56:10: fatal error: 'gmp.h' file not found #include "gmp.h" ^ 1 error generated.
This is all the more infuriating, because homebrew does install the GMP header file in
/usr/local/include as a dependency of ECL (see later).
Furthermore, at first start SLIME will hence display an infinite number of polling messages in your minibuffer, as it can’t connect to the SWANK server that ECL should have compiled.
If you install the xcode command-line tools by typing the following in a term and following the prompts:
… the clang compiler will henceforth also search in
/usr/local/include, avoiding the error above when you startup slime for the first time.
Install ECL using homebrew. This will also pull down and install
brew install ecl
In Emacs, install the SLIME package by doing
M-x package-install RET slime RET
After having done this, add the following two lines to your Emacs
(setq inferior-lisp-program "ecl") (setq slime-contribs '(slime-fancy))
Without that second line, starting slime will only get you an
*inferior-lisp* buffer, and not the
*slime-repl ECL* that you deserve!
You can either eval those two lines and then continue with the next step, or restart Emacs if you’re the restarting type. (too much Windows can do that to you)
In Emacs, do
M-x slime RET, at which point you should find yourself in a lovely
*slime-repl ECL* buffer, where you can talk to the inferior Lisp process (it’s being modest, because it’s actually superior) with syntax completion, real-time code documentation and other niceties.
You can now enter some Lisp code in a new
.lisp buffer, for example something like this:
(defun harmonic_tr (n accum) "tail-recursive harmonic function - cpb" (if (<= n 1) (+ accum 1) (harmonic_tr (- n 1) (+ accum (/ 1.0 n))))) (time (loop for i from 1 to 10 collect (harmonic_tr 1000000 0)))
You can now put your cursor anywhere on the
harmonic_tr function, and press
C-c C-c (
M-x slime-compile-defun), and then on the time loop (hehe), to have it calculate the millionth harmonic number ten times. As you do this, various outputs will appear in the REPL. You can also switch to the REPL, and continue experimenting with your newly defined functions over there.
This specific function is due to some completely-for-fun micro-benchmarks I did in 2005 with OCaml, Octave, Python, Pyrex (what later became Cython), and C to compare with Lush. Then I used the harmonic number, because that’s what Lush had on its website as a demonstration of Lush’s looping speed.
Fast forward 10 years, and on this early 2015 MacBook Pro with a 2.7GHz i5 and Embeddable Common Lisp (ECL) 16.0.0 I get 0.0736 seconds per call to
harmonic_tr(1000000), averaged over 10 calls. The
loop version does marginally better.
For comparison, the straight Python 3.5.1 loop code does 0.1187, a tail-recursive version in Guile 2.0.11 Scheme does 0.0904 seconds (ask me in the comments if you’d like to see this), the
clang -O3 loop code in C does 0.0033, and PyPy 2.6.0 on the Python code does 0.0024 seconds.
This micro-benchmark is fun, but do remember that when you’re embedding a dynamic language, you are usually focusing not only on raw performance, but on other matters like the language’s expressivity and, more importantly, on the best impedance match for you extension writer and their domain.