Working with pp: the Ruby Pretty Printer
Message from 2022
This post is pretty old! Opinions and technical information in it are almost certainly oudated. Commands and configurations will probably not work. Consider the age of the content before putting any of it into practice.
Last week, I worked on making the Riak CRDTs print out nicer in the Riak Ruby client. Since my main interaction with riak-client
is through the pry
REPL and RSpec, I decided to use the pp
stdlib library to make the output nicer.
Out of the box, a Riak CRDT set looked like:
#<Riak::Crdt::Set:0x007fbec39d87b0 @bucket=#<Riak::Bucket {bucketname},
@key="cats", @bucket_type="sets", @options={}, @dirty=true>
This was terrible for several reasons: the actual value of the set wasn’t listed, the object_id
is a ridiculous thing, and the bucket type/bucket/key relationship is hard to read. The end goal was something that made it easy to identify which object was which, what it contained, and elide unimportant implementation details.
Ruby’s pp
library has some sensible defaults: print out the class name, object_id
, and all instance variables. It’s relatively good for objects built out of primitives, but tends to break down on deep data structures. The CRDTs for Riak tend to be deeply-nested, especially the Map type, which can contain Counters, Flags, Maps (which can continue recursing down), Registers, and Sets.
PP Callbacks
The way PP works is a bit convoluted.
- You call
pp some_object
-
Kernel.pp
callsPP.pp some_object
-
PP.pp
instantiates aPP
instance, and callsPP#pp some_object
-
PP#pp
callssome_object.pretty_print self
orsome_object.pretty_print_cycle self
Working with PP on your own objects isn’t convoluted: create a pretty_print
method that takes a PP
instance as its only argument, and calls methods on the instance to output what your users need.
In the case of a recursion, you don’t simply want to repeat the same full representation every time, just enough to know that there’s some recursion.
PP Methods
I ended up using very few methods on PP
:
-
object_group
wraps its contents with angle brackets and general object information -
breakable
inserts a space that can be used as a line break -
comma_breakable
inserts a comma followed by a breakable space -
text
inserts text -
pp
recursively pretty-prints the given value
Examples
On Riak::Crdt::Base
, I wanted to provide the basics that Counter
, Map
, and Set
can inherit from. This method prints out the given class, its bucket type, bucket, key, and then yields to the child class:
# on Riak::Crdt::Base
def pretty_print(pp)
pp.object_group self do
pp.breakable
pp.text "bucket_type=#{@bucket_type}"
pp.comma_breakable
pp.text "bucket=#{@bucket.name}"
pp.comma_breakable
pp.text "key=#{@key}"
yield
end
end
On Riak::Crdt::Map
, which inherits from Base
above, I wrap map-specific fields in the above method. Since it has five sub-collections, I loop over those to pretty-print them individually.
# on Riak::Crdt::Map
def pretty_print(pp)
super pp do
%w{counters flags maps registers sets}.each do |h|
pp.comma_breakable
pp.text "#{h}="
pp.pp send h
end
end
end
Riak::Crdt::InnerMap
doesn’t have a bucket type, bucket, or key. It’s a simple version of what Map
does:
# on Riak::Crdt::InnerMap
def pretty_print(pp)
pp.object_group self do
%w{counters flags maps registers sets}.each do |h|
pp.comma_breakable
pp.text "#{h}="
pp.pp send h
end
end
end
Conclusions
Pretty-printing is really nice for debugging and playing with Ruby in a REPL. On top of adding pretty-print support to your code, maybe also encourage users to use Pry, which does a great job of colorizing output.