Debux is a simple but useful library for debugging Clojure and ClojureScript. I wrote this library to debug my own Clojure(Script) code and to analyze other developer’s Clojure(Script) code.
To include debux in your project, simply add the following to your project.clj dependencies:
[philoskim/debux "0.2.0"]
In Clojure, the following line has to be included in your file to use debux library.
(use 'debux.core)
In ClojureScript, the following (:require …)
line has to be included in your file to use debux library.
(ns example.core
(:require [debux.cs.core :refer-macros [clog dbg]]))
When debugging the thread-first macro ->
or thread-last macro ->>
, debux prints every expression in the thread macros.
This is an example of thread-first macro ->
.
(dbg (-> "a b c d"
.toUpperCase
(.replace "A" "X")
(.split " ")
first))
;=> "X"
dbg: (-> "a b c d" .toUpperCase (.replace "A" "X") (.split " ") first) "a b c d" => "a b c d" .toUpperCase => "A B C D" (.replace "A" "X") => "X B C D" (.split " ") => ["X", "B", "C", "D"] first => "X" => "X"
Another example.
(def person
{:name "Mark Volkmann"
:address {:street "644 Glen Summit"
:city "St. Charles"
:state "Missouri"
:zip 63304}
:employer {:name "Object Computing, Inc."
:address {:street "12140 Woodcrest Dr."
:city "Creve Coeur"
:state "Missouri"
:zip 63141}}})
(dbg (-> person :employer :address :city))
; => "Creve Coeur"
dbg: (-> person :employer :address :city) person => {:name "Mark Volkmann", :address {:street "644 Glen Summit", :city "St. Charles", :state "Missouri", :zip 63304}, :employer {:name "Object Computing, Inc.", :address {:street "12140 Woodcrest Dr.", :city "Creve Coeur", :state "Missouri", :zip 63141}}} :employer => {:name "Object Computing, Inc.", :address {:street "12140 Woodcrest Dr.", :city "Creve Coeur", :state "Missouri", :zip 63141}} :address => {:street "12140 Woodcrest Dr.", :city "Creve Coeur", :state "Missouri", :zip 63141} :city => "Creve Coeur" => "Creve Coeur"
This is an example of thread-last macro ->>
.
(def c 5)
(dbg (->> c (+ 3) (/ 2) (- 1)))
; => 3/4
dbg: (->> c (+ 3) (/ 2) (- 1)) c => 5 (+ 3) => 8 (/ 2) => 1/4 (- 1) => 3/4 => 3/4
If you want to debug one of the expressions in the thread macro ->
or ->>
, don’t do it like this. You will have some exception.
(-> {:a [1 2]}
(dbg (get :a))
(conj 3))
; => java.lang.IllegalArgumentException
; Don't know how to create ISeq from: java.lang.Long
Instead, do it like this.
(-> {:a [1 2]}
(get :a)
dbg
(conj 3))
; => [1 2 3]
dbg: (get {:a [1 2]} :a) => [1 2]
Another example.
(->> [-1 0 1 2]
(filter pos?)
(map inc)
dbg
(map str))
; => ("2" "3")
dbg: (map inc (filter pos? [-1 0 1 2])) => (2 3)
When debugging let
form,
(dbg (let [c (+ 1 2)
[d e] [5 6]]
(-> (+ d e) (- c))))
; => 8
each binding will be printed.
dbg: (let [c (+ 1 2) [d e] [5 6]] ...) c => 3 [d e] => [5 6] => 8
When debugging comp
form,
(def c (dbg (comp inc inc +)))
(c 10 20)
; => 32
the result of each function will be printed.
dbg: (comp inc inc +) + => 30 inc => 31 inc => 32 => 32
In the first place, the following line has to be included in your file to use debux library in Clojure.
(use 'debux.core)
This is a simple example. The macro dbg
prints an original form and pretty-prints the evaluated value on the REPL window. Then it returns the value without interfering with the code execution.
If you wrap the code with dbg
like this,
(* 2 (dbg (+ 10 20)))
; => 60
the following will be printed in the REPL window.
dbg: (+ 10 20) => 30
The dbg
macro can be nested.
(dbg (* 2 (dbg (+ 10 20))))
; => 60
dbg: (+ 10 20) => 30 dbg: (* 2 (dbg (+ 10 20))) => 60
Sometimes you need to see several forms evaluated. To do so, a literal vector form can be used like this.
(let [a (take 5 (range))
{:keys [b c d] :or {d 10 b 20 c 30}} {:c 50 :d 100}
[e f g & h] ["a" "b" "c" "d" "e"]]
(dbg [a b c d e f g h]))
; => [(0 1 2 3 4) 20 50 100 "a" "b" "c" ("d" "e")]
dbg: [a b c d e f g h] => {:a (0 1 2 3 4), :b 20, :c 50, :d 100, :e "a", :f "b", :g "c", :h ("d" "e")}
Notice that the printed value is a map, not a vector and the form is prepended with colon to differenciate the form from the evaluated value.
Further examples:
(def a 10)
(def b 20)
(dbg [a b [a b] :c])
; => [10 20 [10 20] :c]
dbg: [a b [a b] :c] => {:a 10, :b 20, :[a b] [10 20], ::c :c}
The various options can be added and combinated in any order after the form.
You can add your own message in a string and it will be printed betwen less-than and more-than sign like this.
(dbg (repeat 5 (dbg (repeat 5 "x")
"inner repeat"))
"outer repeat")
; => (("x" "x" "x" "x" "x")
; ("x" "x" "x" "x" "x")
; ("x" "x" "x" "x" "x")
; ("x" "x" "x" "x" "x")
; ("x" "x" "x" "x" "x"))
dbg: (repeat 5 "x") <inner repeat> => ("x" "x" "x" "x" "x") dbg: (repeat 5 (dbg (repeat 5 "x") "inner repeat")) <outer repeat> => (("x" "x" "x" "x" "x") ("x" "x" "x" "x" "x") ("x" "x" "x" "x" "x") ("x" "x" "x" "x" "x") ("x" "x" "x" "x" "x"))
If you don’t specify the number after the form, debux will print only 100 items in each collection by default.
(dbg (range 200))
; => (0 1 2 ... 199)
dbg: (range 200) => (0 1 2 3 4 ...... 98 99 ...)
So, if you want to print more than 100 items, specify the number option explicitly.
(dbg (range 200) 200) ; => (0 1 2 ... 199)
dbg: (range 200) => (0 1 2 3 4 ...... 198 199)
Especially in the case of evaluating an infinite lazy-seq, you have to specify the number of the elements to print, to avoid the OutOfMemoryError
.
(dbg (range) 5)
; => (0 1 2 3 4)
dbg: (range) => (0 1 2 3 4)
If you omit the number in evaluating an infinite lazy-seq, it will print default 100 elements but cannnot avoid OutOfMemoryError
.
(dbg (range))
; => Unhandled java.lang.OutOfMemoryError Java heap space
dbg: (range) => (0 1 2 3 4 5 6 7 8 9 ...... 98 99 ...)
So Be careful! You have to limit the number of realized infinite lazy-seq explicitly by the number option.
You can use dbg
or clog
macro in REPL window like weasel in ClojureScript. However, you should use clog
instead of dbg
, because clog
macro uses the console.log
fuction of browser’s developer tools to style the form. The evaluated result of dbg
macro will go to the REPL window, and that of clog
macro will go to the browser’s console.
The following (:require …)
line has to be included in your file to use debux library in ClojureScript.
(ns example.core
(:require [debux.cs.core :as d :refer-macros [clog dbg break]]))
(clog (repeat 5 (clog (repeat 5 "x")
"inner repeat"))
"outer repeat")
Besides 'Usage in Clojure' features, you can use additional options in clog
macro.
You can style the form, using the following predefined keywords.
keyword | abbreviation |
---|---|
:style |
:s |
:error |
:e |
:warn |
:w |
:info |
:i |
:debug |
:d |
(clog (+ 10 20) :style :error "error style") (clog (+ 10 20) :style :warn "warn style") (clog (+ 10 20) :style :info "info style") (clog (+ 10 20) :style :debug "debug style") (clog (+ 10 20) "debug style is default")
You can redefine the predefined styles or define your own new style like this.
(d/merge-style {:warn "background: #9400D3; color: white"
:love "background: #FF1493; color: white"})
(clog (+ 10 20) :style :warn "warn style changed")
(clog (+ 10 20) :style :love "love style")
;; You can style the form directly in string format in any way you want.
(clog (+ 10 20) :style "color:orange; background:blue; font-size: 14pt")
If you add :once
(or :o
in brief) option after the form, the same evaluated value will not be printed. This is a very useful feature, when you are debugging a game programming, where successive multiple frames usually have the same evaluated value.
(def a (atom 10))
;; This will be printed.
(clog @a :once)
;; This will not be printed,
;; because the evaluated value is the same as before.
(clog @a :once)
(reset! a 20)
;; This will be printed,
;; because the evaluated value is not the same as before.
(clog @a :once)
;; This will not be printed,
;; because the evaluated value is the same as before.
(clog @a :once)
You can use both dbg
and clog
macros on the browser REPL like weasel. The following is an example about runing the browser REPL weasel.
;; project.clj
(defproject example "0.2.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.8.0"]
[org.clojure/clojurescript "1.7.228"]
[com.cemerick/piggieback "0.2.1"]
[weasel "0.7.0"]
[philoskim/debux "0.2.0"]]
:plugins [[lein-cljsbuild "1.0.5"]
[lein-figwheel "0.3.7"]]
:source-paths ["src/clj"]
:clean-targets ^{:protect false}
["resources/public/js/app.js"
"resources/public/js/app.js.map"]
:repl-options {:nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]}
:cljsbuild {:builds [{:id "dev"
:source-paths ["src/cljs"]
:figwheel true
:compiler {:main "example.brepl"
:asset-path "js/out"
:output-to "resources/public/js/app.js"
:output-dir "resources/public/js/out"
:source-map true
:optimizations :none} }]})
;; example/brepl.cljs
(ns example.brepl
(:require [cljs.debux :refer-macros [dbg clog break]]
[weasel.repl :as ws-repl] ))
(ws-repl/connect "ws://localhost:9001")
Aftr that, you have to evaluate the following forms in your REPL window to run the browser REPL weasel.
user> (require '[weasel.repl.websocket :as ws] '[cemerick.piggieback :as pback]) nil user> (pback/cljs-repl (ws/repl-env :port 9001)) << started Weasel server on ws://127.0.0.1:9001 >> << waiting for client to connect ...
Refresh your browser’s page, and then you will see the following in your REPL window.
<< waiting for client to connect ... connected! >> To quit, type: :cljs/quit cljs.user>
Now you can do anything in this REPL as in the Clojure REPL. When you evaluate dbg
macro in your ClojureScript source code, the result will go to the REPL window and when you evaluate clog
macro in your ClojureScript source code, the result will go to your browser’s console window.