Clojure でアプリケーション開発を行なう場合、 REPL を酷使するわけなんですがその中でもハマりやすい問題が幾つかあることに最近同僚を見ていて気付いたので書いておきます。
マルチメソッドが再定義できない
例えば次のようなマルチメソッドを定義していたとします。
(defmulti greeting (fn [time name] time))
(defmethod greeting :morning
[_ name]
(str "おはよう、" name))
(defmethod greeting :afternoon
[_ name]
(str "こんにちわ、" name))
(greeting :morning "あやぴー") ;;=> "おはよう、あやぴー"
(greeting :afternoon "あやぴー") ;;=> "こんにちわ、あやぴー"
シンプルですね。 ですが、ある気紛れで 2 引数うけとっていたのをマップだけを受け取るようにするために次のように変更したとします。
(defmulti greeting :time)
(defmethod greeting :morning
[{name :name}]
(str "おはよう、" name))
(defmethod greeting :afternoon
[{name :name}]
(str "こんにちわ、" name))
(greeting {:time :morning :name "あやぴー"}) ;;=> clojure.lang.ArityException
(greeting {:time :afternoon :name "あやぴー"}) ;;=> clojure.lang.ArityException
既に (defmulti greeting (fn [time name] time))
を評価していた後に、 REPL で (defmulti greeting :time)
を評価すると nil
が返却されると思います。
そして、実際に defmethod
などを評価しても問題ないですが、 greeting
は引数違いというエラーが出て評価できません。
このケースは何が問題なのか気付き難いのですが、一度 greeting
をこのネームスペースから消してあげることで解決できます。こういうときは (ns-unmap *ns* 'greeting)
とします。
require
して別名を付けていたものを名前を変更して同じ別名をつけたらエラーが出る
ちょっと日本語で説明するのが難しいのですが、以下のようなネームスペースがあったとします。
(ns foo.bar
(:require [clojure.string :as str]))
str
という別名を付けて clojure.string
を require
しています。これを評価した後に次のように書き換えて評価します。
(ns foo.bar
(:require [clojure.java.io :as str]))
;; => java.lang.IllegalStateException
;; Alias str already exists in namespace foo.bar, aliasing
;; clojure.string
str
という別名はそのままで clojure.java.io
を require
するようにしました( clojure.java.io
に str
という別名を付けないだろう、っていう突っ込みはなしで)。
このとき例外が吐かれますが、これは分かりやすいですね。単純にその別名は既にこのネームスペースで使われているよ、というだけの話なので (ns-unalias *ns* 'str)
としてあげればよいです。
まとめ
Clojure で開発を行なっていると極力 REPL を再起動したくないと思いますが、このように単純に書き直して再評価すればいいというわけではない場合知っていれば対処できますが、知らないと時間を取られてしまいますし REPL を再起動してしまうと思うので転ばぬ先の杖として知っておいてもらえると良いのかなと思います。
他にも思いついたら加筆すると思います。
余談
Emacs(Cider) では C-c C-u(cider-undef)
を使うことでどちらのケースでも対応することができます。なので Emacs を使いましょう。