読者です 読者をやめる 読者になる 読者になる

(define -ayalog '())

括弧に魅せられて道を外した名前のないプログラマ

Clojure の開発環境をよりシンプルへ ~ inf-clojure 導入編

最近、自宅の ThinkPad X220 で Cider-mode を起動すると segfault が起こるようになった。一度起こるとマシンを再起動するまで JVM が起動できなくなってしまう*1

まぁそういうきっかけがありつつ、開発しているときに Cider を起動するだけで 30 秒程待つ必要がある事実が嫌すぎて*2、原点回帰ということで inf-clojure に乗り換えることにした*3

ちなみに僕が Cider-mode を嫌になった点を幾つか書いておきます。

1. 待ち時間が長い/起動が遅い
これは Clojure 使うなら仕方ないことだけど、やっぱり REPL の起動に時間がかかる。基本的には reload を積極的に使うことで REPL の再起動をする必要はないのだけれど、それでもたまに再起動しないといけないときがある*4。そんなときに重いというのはネックだし、気軽に再起動できないのでやっぱりつらい。

2. 出力先が違う
Cider 使ってる人はわかると思うけど、標準出力とか form を評価したときとかの出力先が違う。 mini-buffer と REPL のそれぞれにいろんなものが出力される。だいたい標準出力とか form を評価したときの出力は mini-buffer で、それ以外が REPL だったかな。
元々 Gauche とか書いていたときは全て REPL に出力されていたので心地良かった。もちろん Cider でも 出力を REPL に出すとかっていう評価の仕方とかあるんだけど、そんなのコマンドいちいち覚えてないし、覚えきれないので全部 REPL にしてくれって思う。

3. nREPL の便利さをあまり教授できていない
正直な話 nREPL をそんなに便利だと思っていないです。 refactor-nrepl は便利だけど、まぁ正直 server/client って分けるより全部 Emacs の中で起動させてしまってええやん感はある。ただ REPL があるだけでいいんです。

4. auto-complete が Cider と連動するのがしんどい
結構いい感じでコード補完してくれるし、 inline document とか便利なんだけど、それを実現するために Cider が Clojure のプロセスと密に連携しているぽくてそれのせいで重かったり、ドキュメントの表示が壊れたり*5、そもそもコード補完されるようになるまでに時間かかったりしてストレスがたまっていた。

5. Cider-mode は複雑すぎる
コマンドたくさんあるし、便利機能もたくさんある、凄く便利です。ただし、それを使いこなすことが出来てマシンパワーが潤沢にある場合に限る。便利なツールキットではあるけど日常的に使ってる機能ってそんなに多くなくて、僕なんかだと本当に REPL での評価とマクロ展開くらいしか普段使ってない。あと reload コマンドは便利だった。

多分 2,4 あたりが一番大きな理由です。 1 は個人の設定とかのもよるのでなんとも。他にも理由がなくはないけど、だいたいこの人が書いてることと同じかな。
-> Why I don't use cider

日本だと inf-clojure を使ってますっていう人少ないかもしれないので、一応 inf-clojure とはというのを書いておく。

Bozhidar Batsov という Ruby だと Rubocop とかの作者として有名な人が Clojure もバリバリ書いているわけなんですが、彼が 2014 年の Clojure/conj で Rich Hickey と会って少し話をしたそうです。どんな環境を Clojure 書くときに使っているのかと。多分 Clojure 界隈だとこの話は有名だと思うけど Rich Hickey は Cider-mode とか使ってなくて clojure-mode & inferior-lisp-mode だけで Clojure を書いているそうなんですね。 Bozhidar Batsov は少し驚いたようです。何故ならその組み合わせは彼に言わせるととてもひどいものだから。拡張することができないし、 Clojure のための特別なコード補完があるわけでもない。なので彼はそういう Rich Hickey みたいな人のために inf-clojure というマイナーモードを作ることにしたそうです。
Introducing Inf-clojure - a Better Basic Clojure REPL for Emacs - (think)

とどのつまりベースとなった考え方は clojure-mode と inferior-lisp-mode の組み合わせで出来るシンプルで拡張可能なモードというわけです。

ということで前置きが長くなったけど、実際に導入してみます。

github.com

package-install で簡単に

M-x package-install [RET] inf-clojure [RET]

それで

(autoload 'inf-clojure "inf-clojure" "Run an inferior Clojure process" t)
(add-hook 'clojure-mode-hook #'inf-clojure-minor-mode)

みたいに clojure-mode をフックしてあげれば良さそうです。
それからデフォルトで REPL は読み込み専用になっているので、それが嫌なら次の行を足してあげます。

(setq inf-clojure-prompt-read-only nil)

僕は Cask 使ったり、 use-package 使っているので実際の設定はココに書いたこととは違いますが、だいたいこれでいいはずです。
ちなみにキーマップは以下の通り定義されています。

;; inf-clojure-mode-map
"\C-x\C-e" #'inf-clojure-eval-last-sexp 直前の S 式を評価
"\C-c\C-l" #'inf-clojure-load-file ファイルを読み込んで評価
"\C-c\C-a" #'inf-clojure-show-arglist 引数のリストを表示する
"\C-c\C-v" #'inf-clojure-show-var-documentation var のドキュメントを表示
"\C-c\C-s" #'inf-clojure-show-var-source var のソースを表示
"\C-c\M-o" #'inf-clojure-clear-repl-buffer REPL の buffer を綺麗にします

;; inf-clojure-minor-mode-map
"\M-\C-x"  #'inf-clojure-eval-defun S 式の評価( GNU 文化
"\C-x\C-e" #'inf-clojure-eval-last-sexp 直前の S 式の評価
"\C-c\C-e" #'inf-clojure-eval-last-sexp 
"\C-c\C-c" #'inf-clojure-eval-defun 
"\C-c\C-r" #'inf-clojure-eval-region region 選択されている範囲の S 式を評価
"\C-c\C-n" #'inf-clojure-eval-form-and-next 直後の S 式の評価
"\C-c\C-p" #'inf-clojure-eval-paragraph パラグラフでの評価
"\C-c\C-z" #'inf-clojure-switch-to-repl REPL へとスイッチします(カーソル移動)
"\C-c\C-i" #'inf-clojure-show-ns-vars namespace で参照可能な var の表示
"\C-c\C-A" #'inf-clojure-apropos ある var が何処で定義されているか?( foo という名前がどの名前空間で宣言されているかの表示?)
"\C-c\C-m" #'inf-clojure-macroexpand マクロ展開
"\C-c\C-l" #'inf-clojure-load-file
"\C-c\C-a" #'inf-clojure-show-arglist
"\C-c\C-v" #'inf-clojure-show-var-documentation
"\C-c\C-s" #'inf-clojure-show-var-source
"\C-c\M-n" #'inf-clojure-set-ns namespace を切り替える/なければ作る

補完周りに関しては company-mode を採用しているので completion-at-point-functions が定義されていますね。なんか間違いを書いていたみたいです。
ちょっとそのへんも後で乗り換えようかなと検討しています。 Cider だと auto-complete の方が元々サポート強いので、そのまま使えたんだけどちょっと変えたほうが良さそう。

基本的に僕が使っていた機能とかちょっとした便利機能はあらかたあるので、だいたい問題なく使えてます。あとは reload くらいかなーって感じだけど、それはまた記事を改めて書きたいと思います。ちなみに Cider やめたら segfault は起きなくなりました。あと気持ち心地よく使えています。

*1:Let's note CF-LX4 の中に浮かべてる VM はあまり問題ないのでマシンの問題が多少なりあるとは思う

*2:ただでさえ Emacs の起動が遅い( 5 秒くらい?)から嫌なのに!!

*3: Cider の問題というよりは Leiningen の plugins/dependencies を大量に突っ込んでるからとかそういう理由もあるとは思う

*4:ちゃんと reloadable に設計して書けていれば再起動の回数はずっと減る reload の話はこの辺 Blog | My Clojure Workflow, Reloaded | Relevance

*5:最近は inline document をオフってた