___ ___
/\ \ /\ \
/::\ \ /::\ \
/:/\:\ \ /:/\:\ \
/::\~\:\ \ /::\~\:\ \
/:/\:\ \:\__\ /:/\:\ \:\__\
\/__\:\/:/ / \/__\:\/:/ /
\::/ / \::/ /
\/__/ \/__/
A fast, single-namespace, no-dependency pretty-printer for data (not code).
Supports Clojure, ClojureScript, and Babashka.
- Fast1 (~14x-10x faster than
fipp.edn/pprint
and ~85x–16x faster thanclojure.pprint/pprint
at Fipp's benchmark) - Small (under ~500 lines of code, not counting comments)
- Zero dependencies
- Single namespace; either use as a dependency or vendor into your codebase
- Output similar to
clojure.pprint/pprint
- Allocates conservatively (~28x fewer allocations than
clojure.pprint/pprint
, ~12x fewer thanfipp.edn/pprint
)
- Formatting code
- API for large-scale output customization; to customize, vendor and change whatever you want
cl-format
- Syntax coloring (á là Puget)
Either:
-
Copy
src/me/flowthing/pp.cljc
into your codebase and rename the namespace to avoid conflicts, or: -
Pull in via Maven or Git:
;; Maven coordinates me.flowthing/pp {:mvn/version "2024-11-13.77"} ;; Git coordinates io.github.eerohele/pp {:git/tag "2024-11-13.77" :git/sha "13fba3a"}
Then:
user=> (require '[me.flowthing.pp :as pp])
nil
user=> (pp/pprint {:a 1 :b 2 :c 3 :d 4})
{:a 1, :b 2, :c 3, :d 4}
nil
user=> (pp/pprint {:a 1 :b 2 :c 3 :d 4} {:max-width 10})
{:a 1,
:b 2,
:c 3,
:d 4}
nil
user=> (require '[me.flowthing.pp :as pp])
nil
user=> (clojure.repl/doc pp/pprint)
...
The API is stable and there will be no breaking changes. Everything except for me.flowthing.pp/pprint
is internal and subject to change.
Even though pp is not meant for formatting code, having it format the source of every var in the clojure.core
namespace and comparing the output to that of clojure.pprint/pprint
is a good exercise because clojure.core
has a large variety of data structures and nesting levels.
In that exercise, every difference between the outputs of me.flowthing.pp/pprint
and clojure.pprint/pprint
is one where clojure.pprint/pprint
doesn't make full use of the 72 character line width (default for both clojure.pprint/pprint
and pp) even though it could.
Also, unlike clojure.pprint/pprint
, pp prints records like pr
does:
user=> (defrecord R [x])
user.R
nil
user=> (prn (->R 1))
#user.R{:x 1}
nil
user=> (pp/pprint (->R 1))
#user.R{:x 1}
nil
user=> (clojure.pprint/pprint (->R 1))
{:x 1}
nil
Furthermore, clojure.pprint/pprint
prints Java arrays with commas, pp prints them without:
user=> (clojure.pprint/pprint (char-array [\c \p \p]))
[\c, \p, \p]
user=> (me.flowthing.pp/pprint (char-array [\p \p]))
[\p \p]
In addition, there are one or two other minor, insignificant differences in where clojure.pprint/pprint
and pp insert line breaks. If you spot these and they bother you, file an issue.
- pp uses
print-method
for pretty much everything except Clojure's built-in collection types. This means pp, unlike Fipp, prints things like time-literals the same way asclojure.pprint/pprint
. - Fipp prints
(fipp.edn/pprint '@foo)
as(clojure.core/deref foo)
; pp, likeclojure.pprint/pprint
, prints it as@foo
. The same withquote
/'
,var
/#'
, andunquote
/~
.
- ClojureScript doesn't support
*print-dup*
, so neither does pp.
The algorithm pp uses is based on the ideas in Pretty-Printing, Converting List to Linear Structure by Ira Goldstein (Artificial Intelligence, Memo No. 279 in Massachusetts Institute of Technology A.I. Laboratory, February 1973).
Footnotes
-
I have only benchmarked the Clojure implementation. Cursory testing with
simple-benchmark
shows a ~6x improvement overcljs.pprint/pprint
, but grain of salt and all that. ↩