Skip to content

brandonbloom/fipp

Repository files navigation

Fast Idiomatic Pretty-Printer

Fipp is a better pretty printer for Clojure and ClojureScript.

Like clojure.pprint, this pretty printer has alinear runtimeand uses bounded space.However, unlike clojure.pprint, Fipp's implementation is tuned for great performance and has a functional, data-driven API.

The data interface is agnostic to the source language. Printers are included for Edn data and Clojure code, but it is easy to create a pretty printer for your own language or documents: Even if they're not made out of Clojure data!

Fipp is great for printing large data files and debugging macros, but it is not suitable as a code reformatting tool. (explanation)

Installation

Fipp artifacts arepublished on Clojars.

To depend on this version with Lein, add the following to yourproject.clj:

[fipp"0.6.26"]

This version of Fipp works with Clojure 1.7 or newer.

Seethe v0.5 branchfor a version of Fipp that works with Clojure 1.6.

ClojureScript is supported from build 3269 and up.

Colorization & REPL Integration

Pugetuses Fipp's engine to provide an alternative, colorizing printer.

Whidbeyintegrates Puget in to nREPL via Leiningen, so that every evaluation pretty prints in color.

Printer Usage

;;Refer with a rename to avoid collision with your REPL's pprint.
(require'[fipp.edn:refer[pprint]:rename{pprint fipp}])

(fipp[123])
(fipp(range50))
(fipp(range20))
(fipp(range20) {:width10})

(require'[fipp.clojure])
(fipp.clojure/pprint'(let[foo"abc 123"
bar {:x1:y2:z3}]
(do-stufffoo (assocbar:w4)))
{:width40})

The available options are:

  • :widthdefaults to70.
  • :writerdefaults toclojure.core/*out*(Clojure only).
  • :print-fndefaults tocljs.core/*print-fn*(ClojureScript only).
  • :print-lengthbehaves as and defaults toclojure.core/*print-length*.
  • :print-levelbehaves as and defaults toclojure.core/*print-level*.
  • :print-metabehaves as and defaults toclojure.core/*print-meta*.

Any other supported/hidden options are subject to change.

Conveniences

Thedbgmacro can be used for convenient "printf debugging" of source file, line, expression, and evaluation result to*err*.

(require'[fipp.edn:refer[dbg]])
(dbg(repeat5(range10)))

This will print:

/path/to/that/file.clj:2
(repeat 5 (range 10))
=>
((0 1 2 3 4 5 6 7 8 9)
(0 1 2 3 4 5 6 7 8 9)
(0 1 2 3 4 5 6 7 8 9)
(0 1 2 3 4 5 6 7 8 9)
(0 1 2 3 4 5 6 7 8 9))

A Fipp-enabled version ofclojure.repl/pstis also available:

user=> (require '[fipp.repl:refer [pst]])
user=> (throw (ex-info "whoops" {:xs (range 20):ys (range 20)}))

ExceptionInfo whoops clojure.core/ex-info (core.clj:4617)
user=> (fipp.repl/pst)
ExceptionInfo whoops
{:xs (0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19),
:ys (0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19)}
clojure.core/ex-info (core.clj:4617)
clojure.core/ex-info (core.clj:4617)
user/eval3185 (form-init1248204588518004004.clj:1)
user/eval3185 (form-init1248204588518004004.clj:1)
clojure.lang.Compiler.eval (Compiler.java:6927)
clojure.lang.Compiler.eval (Compiler.java:6890)
clojure.core/eval (core.clj:3105)
clojure.core/eval (core.clj:3101)
clojure.main/repl/read-eval-print--7408/fn--7411 (main.clj:240)
clojure.main/repl/read-eval-print--7408 (main.clj:240)
clojure.main/repl/fn--7417 (main.clj:258)
clojure.main/repl (main.clj:258)

Fast!

In my non-scientific testing, it has proven to be at least five times as fast asclojure.pprint.It also has the nice property of printing no later than having consumed the bounded amount of memory, so you see your first few lines of output instantaneously.

The core algorithm is described by Swierstra and Chitil in Linear, Bounded, Functional Pretty-Printing.

Swierstra and Chitil's implementation uses lazy evaluation and requires tying the knotto interleave the measuring and printing phases to achieve the bounded space goal.

However, this implementation is instead a port of the strict evaluation strategy as described by Kiselyov, Peyton-Jones, and Sabry in Lazy v. Yield: Incremental, Linear Pretty-printing.

Clojure's transducers are used to simulate generators and theiryield operator. Unlike lazy reduction, transducers interleave execution of multi-phase transformations by function composition. This enables preservation of the bounded-space requirement and eases reasoning about the program's behavior. Additionally, it avoids a lot of intermediate object allocation.

Idiomatic!

Clojure's included pretty printer supports pluggable dispatch tables and provides an API for controlling the printing process. The programming model is side-effectual. For example, to print a breaking newline, you execute (pprint-newline:linear).This means that it's a difficult and tricky process to write or compose new pretty printers.

Fipp, on the other hand, accepts a "pretty print document" as input. This document is similar to HTML markup usinghiccup.

Here are some examples:

(require'[fipp.engine:refer(pprint-document)])

(defnppd[doc]
(pprint-documentdoc {:width10}))

(ppd[:span"One":line"Two":line"Three"])

(ppd[:group"(do"[:nest2:line"(step-1)":line"(step-2)"]")"])

If you want to write your own printer, see doc/primitives.mdfor details.

License

Copyright © 2015-2021 Brandon Bloom

Distributed under the Eclipse Public License, the same as Clojure.

Acknowledgements

Fipp is fast in part thanks toYourKit's Java Profiler.