Mohegan SkunkWorks

Fri, 12 Mar 2010 15:03:57 EST

Adding clojure to an existing slime setup in emacs

The current recommended setup of emacs and slime with clojure is to have elpa handle all the dependencies. As an alternative, you can start a swank server using either the swank plug-in for the leiningen build tool, or the swank plug-in for the maven build tool.

All of this advice is good, but I've been using slime with sbcl and emacs for years and I don't want to start from scratch just to add clojure. In addition, rather than hand things off to a tool like elpa, I'd like to install things myself, so I get to understand how the various pieces work together.

I'm going to show how you too can use use the current cvs head for slime, and the current git repos for clojure, clojure-contrib and swank clojure to run clojure with slime and lisp. I"ll provide a few helpful links to get more information on slime and swank. As it turns out, there 's currently a bit of incompatibility between clojure and the slime package, but it's minor and easy to work around.

A few words on slime 's architecture

Bill Clementson's blog entry on slime brings together quite a few resources on slime. It has this illuminating illustration of slime's architecture taken from Tobias Rittweiler's slime talk. Slime/Swank Architecture

As you can see slime's a client-server architecture. Each lisp needs to implement the swank protocol in order to talk to the emacs client.Swank clojure provides a swank implementation for clojure. Typically you start a 'swank' session of your lisp through emacs. But you can also choose to start up a lisp, load it's swank module, start a swank server and connect to it from emacs. That's the method used by the leiningen and maven plugins mentioned earlier. This picture underscores that there are various pieces that work together through a shared protocol.

Emacs and Slime

I installed slime from it's cvs repository in ~/Tools/slime.Slime's _./contrib_ sub-directory contains addons to enhance slime's basic functionality. In particular _slime-fancy.el_ groups a large set of these enhancements together so you don't have to initialize each and everyone of them. I"ll get back to this later on.By the way, _./contrib_ also contains swank implementations for ruby, mit-scheme and kawa. Unfortunately none of these work satisfactorily.

The slime manual has a section on how to setup slime for use with multiple lisps. This involves adding entries like this

       (name (progam progam-args..) &key coding-system init init-function env)  

to slime-lisp-implementations. name is the symbol used to identify the program. (program program-args ...) is used to start up the lisp in question. The function specified by the keyword :init is used to instruct the lisp to start its swank server.

An entry in the emacs initialization file to enable multiple lisps with slime might look like this:

     (add-to-list 'load-path "/home/fons/Tools/slime/")  
     (add-to-list 'load-path "/home/fons/Tools/slime/contrib")  
 
     (require 'slime)  
     (slime-setup '(slime-fancy slime-asdf))  
 
     (setq slime-multiprocessing t)  
     (set-language-environment "UTF-8")  
     (setq slime-net-coding-system 'utf-8-unix)  
 
     (setq slime-lisp-implementations  
       '((clisp   ("/usr/bin/clisp" "-K full"))  
         (sbcl    ("/usr/bin/sbcl"))  
         (abcl    ("~/Tools/bin/abcl"))  
         (ccl     ("~/Tools/bin/ccl"))))  
 
     (setf slime-default-lisp 'sbcl)  
     (global-set-key [f6] 'slime)  
 

First slime's directories are added to emacs' load-path. After loading slime and adding a bunch of useful add-ons by loading slime-fancy, a few lisps are added to slime-implementations. Some of them run from standard linux locations. Others are started with a shell script. slime-default-lisp i set to sbcl so that's the default list that's going to used when I just do M-x slime (or hit f6 since I've bound this command to this key).

The other lisps are started by invoking slime with a negative prefix argument, M-- M-x slime and selecting the name of the lisp in the slime-defaults-list.

If you 've never really worked with slime it might be good idea to set up at least a few lisps just to test out your setup and get you in the right mood for the additional hacking which needs to be done.

swank-clojure

I cloned the swank clojure git repository and build it. I put all the clojure related specif components in a directory called "~/Tools/swank-clojure-enablers". The swank-clojure-autoload file is obviously not part of the git repository and needs to be generated. After adding this

(defun swank-clojure-autoloads nil  
(interactive)  
(let ((generated-autoload-file "~/Tools/swank-clojure-enablers/swank-clojure-autoload.el"))  
(update-directory-autoloads "~/Tools/swank-clojure-enablers"))) 

to my init file M-x swank-clojure-autoloads can be used to generate swank-clojure's autoload file. As you can see in the source file for swank-clojure.el defadvice is used to add clojure to slime-lisp-implementations :

(defadvice slime-read-interactive-args (before add-clojure)  
;; Unfortunately we need to construct our Clojure-launching command  
;; at slime-launch time to reflect changes in the classpath. Slime  
;; has no mechanism to support this, so we must resort to advice.  
(require 'assoc)  
(aput 'slime-lisp-implementations 'clojure  
(list (swank-clojure-cmd) :init 'swank-clojure-init)))  
 

What's basically happening here is that the keyword 'clojure is added to slime-lisp-implementations. The function swank-clojure-cmd starts up clojure and the function swank-clojure-init instructs clojure to start up a swank server.

All the heavy lifting as far as customizing your clojure environment is done in the swank-clojure-init function. It basically sets up the class path and swank-clojure.el provides hooks to customize the start up phase.

Setting the class-path through swank-clojure-classpath.

The first time clojure is started it checks to see if the jar files are installed so that clojure can be run with swank. If it doesn't 'see' the jars installed it will download a precompiled version of clojure.jar, clojure-contrib.jar and clojure-swank.jar to ~/.swank-clojure.

My preference is to use the latest and greatest versions of the various libraries. I also don't like to have jar files put in locations where I can't really see them. I'd rather know explicitly what my dependencies are. Luckily the class-path can be customized through the swank-clojure-classpath variable. After I put some links in ~/Tools/swankclojure-enablers/ to the required jar files, I added this to my emacs initialization file :

   (setq swank-clojure-classpath (directory-files "~/Tools/swank-clojure-enablers" t ".jar$"))  

This highlights an interesting difference with the other lisps. I use asdf to load libraries into an already running lisp image. I don't need to set asdf's paths before doing starting the lisp repl. in fact I can change the asdf search path after the repl has started.

With clojure you're obviously a bit constraint in that you can't change the class path or load a library into the jvm at run time.

swank-clojure provides swank-clojure-project as an alternative way to load your class dependencies at startup. It will kill your clojure repl, set the class path for your project and restart the clojure swank server.

Dealing with the hanging repl

If you start up clojure at this stage and enter something like (+ 1 2) at the cursor you"ll notice that the repl "hangs".

Swank-clojure cannot handle some recent changes introduced to one of to slime addons. The details can be found in the
in the swank-clojure group and slime mailing list The bottom line seems to be that the clojure reader doesn't handle the same collection of characters as other lisp readers.

Luckily this is limited to the autodoc component in .../slime/contrib. autodoc provides fancy markup in the echo area for the arguments of lisp functions.

If you can live without that, you can unload the autodoc component after slime-fancy is loaded. Alternatively you can just use the slime-repl package when you initialize slime.

To summarize

This than is more or less my setup :

 (add-to-list 'load-path "/home/fons/Tools/swank-clojure-enablers")  
 (add-to-list 'load-path "/home/fons/Tools/slime/")  
 (add-to-list 'load-path "/home/fons/Tools/slime/contrib")  
 
 (require 'swank-clojure-autoloads)  
 (require 'slime)  
 (slime-setup '(slime-fancy slime-asdf))  
 
 (unload-feature 'slime-autodoc t)  
 
 (setq slime-multiprocessing t)  
 (set-language-environment "UTF-8")  
 (setq slime-net-coding-system 'utf-8-unix)  
 
 (setq slime-lisp-implementations  
 	    '((clisp   ("/usr/bin/clisp" "-K full"))  
	      (sbcl    ("/usr/bin/sbcl"))  
          (abcl    ("~/Tools/bin/abcl"))  
          (ccl     ("~/Tools/bin/ccl"))))  
 
 (setq swank-clojure-classpath (directory-files "~/Tools/swank-clojure-enablers" t ".jar$"))  
 (setf slime-default-lisp 'sbcl)  
 (global-set-key [f6] 'slime)  
 
 (defun swank-clojure-autoloads nil  
 (interactive)  
 (let ((generated-autoload-file "~/Tools/swank-clojure-enablers/swank-clojure-autoloads.el"))  
 (update-directory-autoloads "~/Tools/swank-clojure-enablers")))  

Notice that I unload autodoc after slime is loaded.