「プログラミングとはデバッグである」
そんな言葉を言っていた人がいました。そして、デバッグで基本となるのが
「とりあえず怪しい変数をプリントしていく」
事ではないでしょうか。
ClojureではREPL(Read Eval Print Loop)が使えるため、データの可視化は容易にできます。しかし、それでも関数が長くなるとその関数の中でどのようなデータが処理されているのか不透明になることもあるかと思います。
そこで、関数内で任意の変数をその変数名と共にプリントするマクロを作りました。
debug
下のマクロdebugは任意個の引数をとり、そられを変数名とともにプリントします。
(defmacro debug
"Prints args. This is useful when debugging."
[& args]
(let [sym (gensym "sym") val (gensym "val")]
`(do
(println "debug information:\n")
~@(map (fn [sym val] `(println ~sym " : " ~val)) (map str args) args)
(println))))
マクロを作成した場合、
macroexpand
を使って、どのようにコードが展開されるのかを確認できます。
(def x 1)
(def y 2)
(macroexpand '(debug x y))
;; => (do
;; (clojure.core/println "debug information:\n")
;; (clojure.core/println sym15155 " : " val15156)
;; (clojure.core/println sym15155 " : " val15156)
;; (clojure.core/println))
実際に上記のコードを評価してみると、以下の結果を得ました。
(def x 1)
(def y 2)
(debug x y)
;; => x : 1
;; y : 2
debugを関数の中に挿入すれば、好きな変数をプリントしその値を確認することができます。
例として、整数のシーケンスを引数にとり、
「偶数のみフィルターしたあとそれらを二乗する」
という変形をする関数 filter-even-then-square を作ってみます。
(defn filter-even-then-square [coll]
(let [original coll
only-even (filter even? original)
squared (map #(* % %) only-even)]
squared))
(filter-even-then-square [1 2 3 4]) ;; => (4 16)
上の例では想定どおりの結果が得られていますが、「途中のデータの変形」については情報がありません。
そこで、debugを挿入するとデータ変形の途中経過を可視化できます。
(defn filter-even-then-square [coll]
(let [original coll
only-even (filter even? coll)
squared (map #(* % %) only-even)]
;; letしたデータをプリント
(debug original only-even squared)
squared))
(filter-even-then-square [1 2 3 4]) ;; => (4 16)
結果は同じですが、repl bufferにdebugに渡したデータが表示されました。
debug information:
original : [1 2 3 4]
only-even : (2 4)
squared : (4 16)
ちなみに、filter-even-then-squareのような関数は、Clojureに標準装備されている「アローマクロ」を使うと簡潔に書けます。
;; 上と同じ関数をアローマクロで作成
(defn filter-even-then-square [coll]
(->> coll (filter even?) (map #(* % %))))
この程度の変形であれば、わざわざデータをプリントする必要はないかもしれませえんが、もっと長い関数を書く場合にはdebugのようなマクロが役に立つのではないでしょうか。
今回の内容は以上になります。最後までお読み頂きありがとございました。