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

日曜プログラミング

休日趣味でやってるプログラミング関連記事をダラダラと

Clojure の leiningen プロジェクトでファイル分割する際の注意点

とある leiningen プロジェクト内でまぁ何となく機能別にファイルを分割したくなりファイルを分割した。

元々 core.clj の 1 ファイル内で REPL 上で試しながらコードを書いていて量が増えてきたので機能別にこんな依存関係で分割した。

foo.core -> foo.a
         -> foo.b -> foo.c

foo.core には単にエントリポイントとしての -main があり、そいつを動作させる為に foo.a と foo.b を参照する必要がある。また、foo.b は foo.c を参照していると言う関係を現している。

この形に分割し、

  1. C-c M-j(cider-jack-in)して REPL 起動
  2. core.clj 内で C-c M-n(cider-repl-set-ns) して名前空間切替
  3. core.clj 内で C-c C-n(cider-eval-ns-form) して ns マクロ評価

としたら、

java.lang.Exception: namespace 'foo.c' not found

と、そんな名前空間ねーよと怒られた。

調べてみると、結論として foo.c がファイル全体で見てまだコンパイルすら通らない書きかけのコードが入っていた、と言うオチ。どうも cider-jack-in する時はファイル全体でコンパイルが通らないのは無視して起動するっぽい。

余談

Clojure がファイル全体を問題なく読めるかどうかは load-file を使うと良いみたい。

;; cider REPL 上での実行結果

foo.core> (load-file "src/foo/a.clj")
;=> #'foo.a/bar

foo.core> (load-file "src/foo/b.clj")
;=> #'foo.b/baz

foo.core> (use 'foo.a)
;=> nil

foo.core> (use 'foo.b)
;=> nil

#'foo.a/bar や #'foo.b/baz と言うのはそれぞれのファイルで最後に評価されたシンボル。

一歩進めてもう少し単純化したのがこれ。

(use 'foo.core :reload-all)

これで芋づる式に foo.core が依存する名前空間全部リロードする。エラーが出るようなら大抵ダメなファイルが分かる例外吐くのでそこを調べると良い。