Clojure 開発におけるデバッグまとめ.

refs: 📝Clojure DX 📝Clojure REPL Driven Development

printデバッグ

古代人から現代まで引き継がれている由緒正しきデバッグ手法, aka. わたしの得意技.

print/println

Clojureでもっとも有名な標準出力関数.

pr/prn/pr-str

オブジェクトの内容をいい感じに出力してくれる.

(println “おれはここだよ”)のようなトレースデバッグにはprintlnをつかい, データの中身をみる(Inspectする)にはprnをつかうのが使い分け.

str

clojure.pprint

Pretty Print for Clojure.

clojure.pprint namespace | ClojureDocs.

(require '[clojure.pprint/pprint :as pp])

pprint: いい感じに出力.

prnでオブジェクトを表示すると, たとえば大きなネストしたMapは1行に表示されて見にくい.その場合は, clojure.pprint.pprintをつかうと見やすくなる.

REPLで (pp) と評価すると最終評価結果が表示される. このppの便利な使い方は, 普通に標準出力した結果が複雑だったときに, 再度ppを通じて表示することでさっきのデータ構造がいい感じになる.

clojure.pprint/print-table

Mapのコレクションをいい感じに表で表示してくれる.

https://clojuredocs.org/clojure.pprint/print-table

Javaのクラスによく自分で実装するtoStringメソッドがClojureにもほしい!

-> print-method というmultimethodをつかう.

toStringをオーバーライドするとJavaのクラスをSystem.out.printlnするときにクラスの内容表示を自分でカスタマイズできる.

clojureでは print-methodというmultimethodが定義されている.

ただし, 基本的にはClojureはデータをMapで扱うことが推奨されているので, そもそもいらないかもしれない. JavaでtoStringが必要だったのはオブジェクトの中身を見る必要があるから. Clojureなら直接Eval!

hashp

https://github.com/weavejester/hashp

hashpをつかうと spy的に変数の内容を補足できる.

コレは大変べんり!

clojure.inspector

Java UIライブラリを使ったデータの描写. 便利らしいが私の環境で動かない…

  • inspect
  • inspect-table
  • inspect-tree

ref. Clojure Inspector · Practicalli Clojure

🔧clojure.core/tap

Clojure1.10から導入された機能. Clojure/ClojureScript両方つかえる.

  • add-tap で出力先を指定.
  • tap> で出力するデータを指定.
  • remove-tapで出力先を削除.

tap自体はEditorやREPLから使うのだが, これと連携するツールが便利. GUIによるデータの可視化が可能になる.

refs. clojure tap - clojure.org


  • Cognitect REBL
    • Site
    • Datomic開発元でおなじみCognitectが開発している.
    • OSSではないものの Stuart HallowayとCognitectのサポートは強そう.
  • Reveal
    • GitHub
    • Read Eval Visualize Loop for Clojure
    • OSSであり REBLの対抗馬.
    • REPLのように振る舞ういVM上のペインでDataを可視化.
      • つまり起動するとJavaアプリとして立ち上がる.
  • Portal
    • GitHub
    • Web技術で開発されている.
    • つまり起動するとChrome PWAアプリとして立ち上がる.
    • revealより後発なので機能的にはrevealが先行している.
    • UIがrevealよりもイケイケ感がある.
      • (revealはEclipse感があり若干のダサさがある).

Instpect Data Structure

prnでデータ構造をとりあえずREPL出力(printlnは文字列表示用).

clojure.pprint/pprintでよりきれいな表示.

表形式(list of maps)は, clojure.pprint/print-tableがよりよい.

🔨tech.ml.datasetはprint-tableよりもさらに見やすい.

Portal

以下は portal でintegrantの状態を表示.

tapについてはだいたい他も同じ.

(require '[portal.api :as p])
(require '[integrant.repl.state :refer [system]])
 
(def p (p/open)) ; Open a new inspector
(add-tap #'p/submit) ; Add portal as a tap> targe
 
(tap> system)
 
(p/clear) ; Clear all values
(remove-tap #'p/submit) ; Remove portal from tap> targetset
(p/close) ; Close the inspector when done

apiの結果を叩くごとに更新するにはこう.

;; 初期化
(def d (p/open))
 
;; tapの代わりにreset!でデータを挿入.
;; するとportal UIに反映される.
(reset! d (get-product {:cid "ssis00335"}))
 
;; Portal UIではなく REPLで表示
@d
 
;; swap!で値の更新.
(swap! d (constantly (get-product {:cid "ssis00333"})))

Namespace探訪

ns-publicsをつかうと, namespaceに定義されている変数を見ることができる.

(ns-publics 'eda)

M-x cider-browse-nsでも同様なことができるが, cider-inspecterと連携するには直接ns-publicsを評価したほうがいいかも.

see more: ✅namespaceにbindingsされたシンボルを確認するには?


定義を消したいときはM-x cider-undefがとても便利.

see more. ✅namespaceから定義を取り除く(cider-undef)

logging

ref: Clojure: Logging

Clojure REPLエラーメッセージまとめ

REPLで評価したときに出てくるエラーメッセージの分類とその対処.

CIDERにおけるerror-buffer

エラーメッセージは cider-error bufferに出力される.

stack-frameはデフォルトでたくさんでてくるが, filterをつかうと見やすくなる.

(setq cider-stacktrace-default-filters '(tooling dup))
;; or
(setq cider-stacktrace-default-filters '(project))

Unhandled clojure.lang.ArityException

関数呼び出し時の引数の数が違う.

  1. Unhandled clojure.lang.ArityException Wrong number of args (0) passed to: xxxx

Topics

Clojureメモリ枯渇/メモリリーク(OOME)調査

基本的にはJVMのノウハウをそのままつかう. 📝JVM:OutOfMemoryError(OOME).

経験則だが, atomを宣言するとその値に対してGCで領域が解放されない. そのため, うっかりloopのなかで繰り返しatomをつかって値を作り続けるとそれがメモリ枯渇を導いた.

または, 無限遅延シーケンスに対してリミットを定めずにシーケンスに値を追加し続けるとメモリ枯渇する. とくにプロダクト投入ではbuffer sizeを設定しておくことは無難.

nREPLを再起動せずにパッケージを追加したい

https://github.com/clj-commons/pomegranate

どうもnREPLにバグがあるようでわたしの環境で実行するとハングし続けたので使うのをやめた. (<2022-08-07 Sun 08:18>)

References