- macOSでCommon Lispの開発環境を構築するには、HomebrewでSBCLをインストールし、EmacsにSLIMEを設定します。
- SLIMEはREPLや補完、デバッガを提供する統合開発環境で、init.elに数行追加するだけで使えます。
- M-x slimeでSBCLが起動し、エディタからC-c C-kでファイルをコンパイルしてそのまま実行できます。
- 関数を書いて即座に評価しながら進める対話的な開発スタイルが特徴です。
1. EmacsとCommon Lisp
macOS環境でCommon Lispの開発環境を構築し、EmacsからSLIMEを使ってコードを書き、コンパイルして実行するまでの基本的な流れを説明します。
Common Lispでは次のような手順で開発を進めることが多くなります。

- Emacsでコードを書く
- SLIMEで関数を評価する
- REPLで動作を確認する
- 必要に応じてファイル全体をコンパイルする
対話的に試しながら開発できる点が特徴です。
1.1. SBCLをインストールする
まずCommon Lisp処理系をインストールします。
macOSではHomebrewを使う方法が簡単です。
brew install sbcl
Code language: Shell Session (shell)

インストール後、次のコマンドで確認できます。
sbcl --version
Code language: Shell Session (shell)
バージョン番号が表示されればインストールは完了です。
1.2. EmacsにSLIMEをインストールする
次にEmacsからLispを操作するための環境を整えます。
SLIMEはEmacs上でCommon LispのREPLや補完、デバッガなどを提供する開発環境です。
Emacsを起動し、次のコマンドを実行します。
init.elに次の設定を追加します。
(use-package slime
:ensure t
:init
(setq inferior-lisp-program "sbcl")
:config
(slime-setup '(slime-fancy slime-company)))Code language: Lisp (lisp)
これでSLIMEがインストールされ、Emacsから起動するLisp処理系としてSBCLを指定しています。
1.3. 【補足】追加の設定(プロンプトの変更)

私は、REPLでいちいち「CL-USER」と表示されるのを短くしたかったので、init.elで「 > 」となるように設定しました。
(with-eval-after-load 'slime-repl
(defun my-slime-repl-insert-prompt ()
(goto-char slime-repl-input-start-mark)
(unless slime-repl-suppress-prompt
(slime-save-marker slime-output-start
(slime-save-marker slime-output-end
(unless (bolp)
(insert-before-markers "\n"))
(let ((prompt-start (point))
(prompt "* "))
(slime-propertize-region
'(face slime-repl-prompt-face
read-only t
slime-repl-prompt t
rear-nonsticky t
front-sticky (read-only)
inhibit-line-move-field-capture t
field output)
(insert-before-markers prompt))
(set-marker slime-repl-prompt-start-mark prompt-start)
(setq buffer-undo-list nil)
prompt-start)))))
(advice-add 'slime-repl-insert-prompt :override #'my-slime-repl-insert-prompt))Code language: Lisp (lisp)
また、SLIMEでもCompany modeでの自動補完を有効にしています。
;; SLIME and Company mode
(use-package slime-company
:ensure t
:after (slime company)
:config
(setq slime-company-completion 'fuzzy))Code language: Lisp (lisp)
あと、REPLでもシンタックス・カラーリングが機能するようにしました。
(defvar slime-repl-font-lock-keywords lisp-font-lock-keywords-2)
(defun slime-repl-font-lock-setup ()
(setq font-lock-defaults
'(slime-repl-font-lock-keywords
nil nil (("+-*/.<>=!?$%_&~^:@" . "w")) nil
(font-lock-syntactic-face-function
. lisp-font-lock-syntactic-face-function))))
(add-hook 'slime-repl-mode-hook #'slime-repl-font-lock-setup)Code language: Lisp (lisp)
2. SLIMEを起動する
Emacsで次を実行します。
M-x slime
SBCLが起動し、REPLが表示されます。
REPLは入力した式を評価し、結果をその場で表示する対話環境です。
例えば次の式を入力します。
(+ 1 2 3 4)
Code language: Lisp (lisp)

結果は 10 と表示されます。
2.1. エディタとREPLの連携の仕方
REPLをそのまま動かしても良いのですが、ちゃんとエディタにLispコードを残したいですよね。
たとえば、CやJavaでは「ファイルを書いてコンパイルして実行する」が基本で、コードは最初からファイルに存在します。
しかし、LispではREPLを使った評価の方法を最初に習います。
これは、REPLで試しながらコードを育てて、よくなったものをファイルに定着させていく流れだからです。
もちろん、Lispでもコードは最終的にファイルに残します。
REPLは、作業机。
「その場限りの遊び場」ではなく、ファイルに書いた定義を1個ずつ送って即座に確認するのが、その使い方なのです。
LispファイルとREPLの役割分担はこうなります。
Lispファイルにはdefunやdefstructなど、残したい定義を書きます。
これをREPLに送って、ちゃんと意図通りに動くか、観察します。
Emacsで hello.lisp というファイルを作成します。
(defun main ()
(format t "Hello Lisp~%"))Code language: Lisp (lisp)
defunなどのトップレベル定義のなかで、C-c C-c を押すと、REPLで使えるようになります。
また、関数を書き直したらその中にカーソルを置いてC-c C-cを押すだけで、関数定義を更新できます。
C-c C-c
現在の関数をコンパイルしますC-c C-r
選択した範囲をREPLで評価しますC-M-x
インタープリタ経由で定義を評価しますC-c C-k
ファイル全体をコンパイルしてロードします
定義ではなく任意の式を送りたいときは、リージョン選択してC-c C-rを使います。
一時的なletやloopなど、ファイルに残すつもりのないコードもそのまま流せます。
;; これを選択して C-c C-r で送る
(let ((xs '(1 2 3 4 5)))
(reduce #'+ xs))
Code language: Lisp (lisp)

C-M-xも定義を評価しますが、コンパイルではなくインタープリタ経由で動きます。C-c C-cより最適化が入らないぶん、デバッグ情報が残りやすく、エラーの原因を追いたいときに向いています。
3. エラーとデバッガ(SLDB)
実行時エラーが起きると、SLIMEはSBCLのデバッガをEmacsのバッファとして表示します。
このバッファはSLDBと呼ばれます。
たとえばゼロ除算を起こすと、こんなエラーが出ます。
(/ 10 0)
Code language: Lisp (lisp)
SLDBにはエラーの種類、スタックトレース、再起動オプションが並びます。再起動オプションとは「このエラーからどう回復して続行するか」の選択肢で、番号で選べます。ABORTを選べばトップレベルに戻ります。
スタックトレースの各フレームでvを押すと、その時点のソースにジャンプします。REPLとエディタを行き来せずにエラー箇所を確認して修正できます。デバッガから抜けるにはqです。