Fork me on GitHub

PPrint


0.9.0

Example use case of PPrint

PPrint is a module that lets you easily print data structures in a form that is more convenient to use both visually and programmatically. Unlike a traditional .toString, PPrint...

If you've enjoyed using PPrint or any of my other libraries, please consider chipping in to support their ongoing development on Patreon! Any amount helps ensure we can continue improving these libraries going forward.

Getting Started


Add the following to your build config:

// SBT
libraryDependencies += "com.lihaoyi" %% "pprint" % "0.9.0"
libraryDependencies += "com.lihaoyi" %%% "pprint" % "0.9.0" // Scala.js/Native

// Mill
ivy"com.lihaoyi::pprint:0.9.0"
ivy"com.lihaoyi::pprint::0.9.0" // Scala.js/Native

The above example then showed how to use the default pprint configuration. You can also set up your own custom implicit `pprint.Config` if you want to control e.g. colors, width, or max-height of the output.

The three main methods you need to care about are:

You can of course define your own custom methods if you want to e.g. always log to a log file or some third-party service, or if you want your own custom set of debug information to be printed (instead of what .log provides). See the short implementation of these methods if you want ideas on how to write your own versions.

Pretty-printing is by default defined for most standard library types, as well as case classes and case objects. For other types not supported, it falls back to using toString

scala> pprint.pprintln(new Object())
java.lang.Object@54f880c0

You can configure the pretty-printing by defining your own implicit Config object, to consistently set things like maximum width, height or colors across a codebase. However, if you just want to set these things one-off, you can do that too:

scala> pprint.pprintln(Seq(1, 2, 3))
List(1, 2, 3)

scala> pprint.pprintln(Seq(1, 2, 3), width = 5) // force wrapping
List(
  1,
  2,
  3
)

scala> pprint.pprintln(Seq(1, 2, 3), width = 6, height = 3) // truncate
List(
  1,
...

PPrint also provides the pprint.log function, which automatically adds some context so you can find your printouts later:

scala> class Foo{
     |   def bar(grid: Seq[Seq[Int]]) = {
     |     pprint.log(grid)
     |   }
     | }
defined class Foo

scala> new Foo().bar(Seq(0 until 10, 10 until 20, 20 until 30))
pkg.Foo#bar:13 grid: List(
  Range(0, 1, 2, 3, 4, 5, 6, 7, 8, 9),
  Range(10, 11, 12, 13, 14, 15, 16, 17, 18, 19),
  Range(20, 21, 22, 23, 24, 25, 26, 27, 28, 29)
)

Note how the package name, class name, and method name, along with the optional tag. are all formatted nicely for you to read. This should make it much easier for you to find each individual print later. Just like `pprint.pprintln`, the output is nicely formatted across multiple lines and syntax-highlighted for readability.

Streaming


PPrint is lazy and streaming. Unlike println(x.toString) which marshals the entire string in memory before returning it:

scala> val large = Seq.fill(100000)("ABCDE" * 1000)
scala> println(large.toString)
java.lang.OutOfMemoryError: Java heap space

pprint.pprintln streams the result to standard output, meaning that even for enormous data structures like the one above you can immediately start seeing output:

scala> pprint.pprintln(large)
Seq(
  "ABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEA
  BCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABC
  ...

For example, if you have a massive (or even infinite!) data structure and wish to only show a certain amount of it, truncating it is straightforward:

scala> val large = Seq.tabulate(100000)("ABCDE" + _)

scala> pprint.log(large, height=20)
List(
  "ABCDE0",
  "ABCDE1",
  "ABCDE2",
  "ABCDE3",
  "ABCDE4",
  "ABCDE5",
  "ABCDE6",
  "ABCDE7",
  "ABCDE8",
  "ABCDE9",
  "ABCDE10",
  "ABCDE11",
  "ABCDE12",
  "ABCDE13",
  "ABCDE14",
  "ABCDE15",
  "ABCDE16",
  "ABCDE17",
  "ABCDE18",
...

If you want to do something else with the streaming output apart from displaying it on the console, you can also call PPrint.tokenize

scala> val large = Seq.fill(100000)("ABCDE" * 1000)
scala> pprint.tokenize(large)
res0: Iterator[String] = non-empty iterator

This gives you an iterator with which you can do whatever you want: stream it to a file, to your logging system, etc.. Even for extremely large data structures, you can use this data structure to page through it and see what it contains without ever materializing the entire string in memory.

PPrint needs to buffer output in some cases in order to correctly wrap/multiline/truncate output. However, in all cases it should need to buffer at most one line

Customization


By default, you can use the methods pprint.{log, apply, truncate} to pretty-print things to stdout, Strings, or streaming Iterator[String]. Those methods all take optional arguments, if you want one-off customizations to how things are printed. pprint.PPrinter.Color and pprint.PPrinter.BlackWhite are provided as default configurations, where the base pprint methods are an alias for pprint.PPrinter.Color.

If you want to customize the defaults throughout your program, you can do so by creating your own pretty-printer via pprint.copy. This lets you change the default height, width of the pretty-printed output, what colors will be used for highlighting, as well as passing in additionalHandlers to customize pretty-printing of various types.

scala> pprint.pprintln(Math.PI) 
3.141592653589793

scala> val pprint2 = 
  pprint.copy(
    additionalHandlers = { 
      case value: Double =>  pprint.Tree.Literal(f"$value%1.5f") /* 5 digit precision */ 
    }
  ) 

scala> pprint2.pprintln(Math.PI) 
3.14159

TPrint


Apart from pretty-printing values, PPrint also allows you to pretty-print types with the pprint.tprint function:

Example use case of TPrint

Apart from calling tprint yourself, you can also add an implicit TPrint[T] param to a function with a type-parameter T and use TPrint[T]#render(cfg: Config) to pretty-print a type. This is handy if you want to write type-printing functionality to an existing function.

Like value pretty-printing, the colors can be configured by the pprint.Config, with import pprint.Config.Colors._ for colored type-printing and import pprint.Config.BlackWhite._ for non-colored type-printing. You can also provide your own implicit TPrint[T] values if you want to customize the type printing of a particular type.

Example use case of Custom TPrint

Version History


0.9.0

0.7.3

0.7.2

0.7.1

0.7.0

0.6.0

0.5.6

0.5.5

0.5.3

0.5.2

0.5.1

0.5.0

0.4.3

0.4.2

0.4.1

0.4.0

0.3.9

0.3.8

0.3.7

0.3.6

0.3.5

0.3.4

0.3.3

0.3.2

0.3.1

0.3.0