REPLライフをもっと快適に!
∼Stuart SierraのClojureワークフローの紹介∼
Clojure夜会 2014/10/10
@athos0220
REPL!
使ってますか?
user=>
評価
入力した式の結果がすぐ確認できる!
高速なフィードバックループ
Check
user=>
(ns example)
(defn fib [x]
...)
Edit
評価
Check
user=>
(ns example)
(defn fib [x]
...)
Edit
評価
ロード!
or!
リロード
Check
user=>
(ns example)
(defn fib [x]
...)
Edit
評価
修正!
or !
次のステップへ
ロード!
or!
リロード
Check
user=>
(ns example)
(defn fib [x]
...)
Edit
評価
修正!
or !
次のステップへ
ロード!
or!
リロード
失敗
Check
user=>
(ns example)
(defn fib [x]
...)
Edit
修正・確認のフィードバックループを高速に!
回すにはリロードが重要!
評価
修正!
or !
次のステップへ
ロード!
or!
リロード
失敗
リロードにまつわる問題
• プロトコルやマルチメソッド、マクロ等に対する
変更が正しく反映されない
• 名前空間のロード順に制約がある場合に、制約
を満たさない順でロードが実行される
• 通信コネクションの切断・接続やスレッドの停
止・開始等が正しく実行されない
これらを解決するには!
ツールのサポートと設計の工夫が必要
快適なREPLライフへの3ステップ
• tools.namespaceを使おう
• user.cljを作ろう
• コンポーネント単位でシステムを作ろう
tools.namespaceを使おう
• 名前空間の依存関係を解析して適切な順番でロードし直す
• リロードによって不整合が起こらないようにしてくれる
• 平たくいえば (require … :reload) の強化版
A!
B!
A’!
!
C
A’!
B!
C
(require … :reload)
A!
B!
A’!
!
C
A’!
!
C
tools.namespace
例
tools.namespaceを使おう
• 依存ライブラリとして以下を追加(2014/10現在)
!
• (require … :reload) の代わりに (refresh) を使う
!
!
[org.clojure/tools.namespace “0.2.7”]
user=> (require ‘[clojure.tools.namespace.repl :refer [refresh]])	
nil	
user=> (refresh)	
:reloading (reacta.script reacta.scripts.hello)	
:ok	
user=>
user.cljを作ろう
• tools.namespaceはリロード毎に名前空間を作り
直すので…
• REPLで定義した変数・関数が消える
• REPLでrequireした名前空間も見えなくなる
• REPLがクラスパス中のuser.cljを自動で読み込む
ことを利用して、そこに必要なものを定義
user.cljを作ろう
• (準備) :devプロファイルでのみ有効になるパスを作る
• リリース版JARファイルにuser.cljが含まれないように
(defproject foobar "0.1.0-SNAPSHOT"	
:description "TODO"	
:url "TODO"	
:license {:name "TODO: Choose a license"	
:url "http://choosealicense.com/"}	
:dependencies [[org.clojure/clojure "1.6.0"]]	
:profiles {:dev {:dependencies [[org.clojure/tools.namespace
"0.2.7"]]	
:source-paths ["dev"]}})
(ns user	
(:require [clojure.tools.namespace.repl :refer [refresh]]	
[clojure.repl :refer :all]	
[clojure.pprint :refer [pp pprint]]	
[foobar.core :refer :all]))	
!
(def system nil)	
!
(defn init [] (alter-var-root #’system (システム初期化)))	
!
(defn start [] (alter-var-root #'system (システムスタート)))	
!
(defn stop [] (alter-var-root #'system (システムストップ)))	
!
(defn go []	
(init)	
(start))	
!
(defn reset []	
(stop)	
(refresh :after 'user/go))
dev/user.cljの例
コンポーネント単位でシステムを作ろう
• Componentライブラリ
• システムを“コンポーネント”に分割
• コンポーネント=状態を共有する関数群
• DBアクセス、外部サービス、atomやref, etc.
• コンポーネントの起動・停止方法を提供し、依存
関係を考慮した順序で呼び出す
コンポーネント単位でシステムを作ろう
System
DB
Users!
API
Email!
Service
Queue!
Service
コンポーネント単位でシステムを作ろう
(ns example.db	
(:require [com.stuartsierra.component :as comp] …))	
!
(defrecord Database [host port conn]	
comp/Lifecycle	
(start [component]	
(assoc component :conn (db/connect host port)))	
(stop [component]	
(.close conn)	
component))
コンポーネントに関する副作用は!
start/stopメソッド内にまとめる
コンポーネント単位でシステムを作ろう
(ns example.core	
(:require [com.stuartsierra.component :as comp] …))	
!
(defn new-system [config]	
(let [{:keys [host port]} (:db config)]	
(comp/system-map	
:database (database host port)	
:queue-service (queue-service …)	
:email-service (email-service …)	
:users-api (comp/using	
(users-api)	
[:database :email-service]))))
コンポーネント間の依存関係をデータで表現!
Componentが依存関係を考えて各コンポーネントを起動・停止
まとめ
• 動的に変更を取り込むのは動的言語の醍醐味
• 各ステップを単独で導入するだけでも効果あり
• 高速なフィードバックループを実現し、快適な
REPLライフを!
参考文献
• My Clojure Workflow, Reloaded
• Clojure in the Large [InfoQ]
• tools.namespace [GitHub]
• Component Library [GitHub]

REPLライフをもっと快適に