Overview
📝REPL駆動開発はLISP言語の特徴だが, とくにClojureにフォーカスしたをまとめる.
またREPLを活用したClojureの開発ノウハウもまとめていく.
EmacsでのClojure REPL Driven Developmentの具体的な方法
- M-x cider-jack-in (REPLにEmacsから接続)
- M-x cider-load-file (ファイル単位でevaluate)
- M-x cider-eval-last-sexp (フォーム単位でevaluate & インラインで結果表示)
- M-x cider-eval-last-sexp-to-repl(フォーム単位でREPLに送信してevaluate)
ref: 🔗Column: REPL 駆動開発を取り入れて Ring でもう少し遊んでみる — Clojure の日本語ガイド
📝Reloaded Workflow
Stuart Sierra さんの提唱で有名になった?開発手法(2013).
ref: Clojure Workflow Reloaded
Clojureの起動, 特にJVMの初回立ち上げが重くて気軽に再起動できないからこそ生まれた工夫.
アプリをシングルトンなJVMとして起動するのではなくてインスタンスとして起動することで古いインスタンスは捨ててしまう(i.e.ガーベージコレクションかな?). とくにresetを連発してライトにシステム再起動.
🔧tools.namespace/refresh
鍵となるのは clojure.tools.namespaceといライブラリ.
ココに定義されている refresh を活用する.
(require '[clojure.tools.namespace.repl :only [refresh]])
古くはuse, 今はrequireをつかうことでコードを評価してREPLに動的更新をかけることは可能だが, あるnamespaceとの依存関係が崩れるとJVMを再起動しないと復旧しない.
tools.namespaceを利用するとnamespaceの依存関係を考慮して関連namespaceもreloadしてくれるので, JVMを再起動することなくプロジェクト全体のソースコードのリロードを実現できる.
set-refresh-dirsをしたあとにrefreshを一度走らせないと初回で全部reloadされる
ハマりポイント.
set-refresh-dirsをしたあとにフォルダの更新の差分チェックが始まる. すると, 初回のrefreshでは元となる差分がないためすべてがreloadされる.
これを防ぐにはset-refresh-dirsをしたあとに一度refreshを走らせる.
状態管理ライブラリの代わりにnamespaceにbindingされたatomをつかうと消える
ちょっと実験的に機能を追加するときは, Clojure: 状態管理ライブラリをつかわないで, 脳死でnamespaceにatomを宣言することは, ままある.
そのとき, tools.namespace.reloadは, ソースコードが編集された場合のみreloadするように作られているので初期化となることがあることは注意.
;; No Global State
(def state-of-world (ref {}))
(def object-handle (atom nil))
たとえdefonceで宣言していたとしても, reloadされるので注意!
see more. 💡defonceとatomとhotreload
💡reloaded workflowによってコンパイルエラーチェック的なやつ
Clojureは🔖動的型付け言語であることが強い特徴. そして静的型付け言語との対比でデメリットとしてよく挙げられるのは, 動かしてみないとコンパイルチェック的なことができないと.
しかし, なんとこのreloaded workflowのように気軽にシステム再起動を高速で繰り返すことによって簡易なコンパイルエラーチェックのようなことは可能.
さらに, clojure.specのinstrument!と組み合わせればより心強い.
💡開発時のReloaded Workflowにinstrumentを組み込む
🤔reloaded workflowはdocker-composeに似ている
コレはたしかによいね, REPLの再起動が結構ストレスだったので. reset 連発するぞ.
docker-composeに発想は似ているな. dockerごとにサービスを定義してdocker-composeで依存関係を制御しつつ立ち上げるところ. docker-composeの再起動ってどのくらいの速さだっけ?
しかしreplのみで再起動のほうが速そうではある. システムテストならdockerまるごと再起動が必要もしれないが少なくもと開発なら気軽にEmacsから再起動したい.
References
- https://github.com/weavejester/integrant-repl
- integrantにおいて reloaded workflowを実践.
- Clojureで快適なREPL駆動開発のために”reloaded workflow”を実践しよう - Qiita
- ミニマリストのためのClojure REST API開発入門2 〜リファクタリング編〜 - Qiita
- integrant-repl deps.edn設定例があった.
See also
clojure.repl
ClojureのREPLでの便利ツールライブラリ.
clojure.repl - Clojure v1.10.3 API documentation
REPLでrequireでライブラリを一切合切ロードしてつかう.
(require '[clojure.repl :refer :all])
- doc: 関数のドキュメントを表示.
- cider-doc/cider-clojuredocs/cider-javadocだとEmacs経由で参照できる.
- source: 関数のソースを表示.
see more. ✅REPLで例外と戦う
Clojure RDD Tips
- 💡REPL上でゴニョゴニョパーサーをかける
- スクレイピングの話.
Design Journal Tips with Clojure CIDER
comment マクロをつかうとまとめてコメントアウトできる.
式の評価結果をそのまま次の行に出力すると後で思い出すときにいちいち評価しなくてもいい.
- M-x cider-pprint-eval-defun-to-comment
- M-x cider-pprint-eval-last-sexp-to-comment
ただしcommentと合わせて使うと評価が動かないのでこれをつかうときはcommentを外す.
💡user.clj便利ツールをいろいろ導入
REPLを起動するとuser.cljが自動的に読み込まれる.
例えばpathに dev/srcを設定してその中に user.cljをおいておくと読み込まれる. そのuser.cljにREPLで利用する便利関数をいろいろ詰め込んでおくと, 自分専用の開発ToolBoxの完成.
:main-optsでnamespaceを指定する方法もある.
Customizing your REPL in clj – Inside Clojure
howto: namespaceの定義を消したい
clojure.coreには, ns-unmap がありこれでnamespaceからsymbolを消すことができる.
ciderだと cider-undefをつかうとbetter.
Clojure RDD Insights
✨ClojureにおいてREST APIのHTTP GET Requestは関数コールに過ぎない
REPL駆動開発において, REST APIの呼び出しは, ちょっと呼び出し後にdelayがあるようなローカル関数の呼び出しと同じノリでつかうことができる. どちらも, エディタでカッコの末尾に移動してEvalするだけ.
取得したデータも結局JSONなのでそれをMapに変換して, これまたREPL上のなにかの変数にbindingしてあとはゴニョゴニョ中身をinspectできる. この気軽さは強い.
(def resp (fetch-some-data url req))
✨大抵のデータはMapに過ぎず中身はinspectでスケスケ見放題
表現は下品だが, これは🔖透明性というもの.
Clojureの世界ではかなりの頻度でMapのデータ構造で処理して, それらはImmutableなデータであり, 中は簡単に覗くことができる.
参考リンク
- ClojureでREPL駆動開発を始めよう - Qiita
- REPL Driven Programming - tyano’s Techlog
- REPL driven development · Practicalli Clojure
- An Architect’s View: Talks: Clojure’s Superpower
- [[https://www.youtube.com/watch?v=skEXGSp10Xs][Clojure-Provo December Meeting - REPL-Driven Development – Clojure’s
Superpower - YouTube]]