Skip to content

The __pdoc__ method¤

The general pattern is the following:

class MyAmazingClass:
    def __pdoc__(self, **kwargs) -> wadler_lindig.AbstractDoc:
        ...  # Create your pretty representation here!

    def __repr__(self):
        # Calls `__pdoc__` and then formats to a particular width.
        return wadler_lindig.pformat(self, width=80)

Let's go ahead and create such an example now! Let's build an example involving mathematical expressions.

import wadler_lindig as wl
class AbstractExpression:
    def __repr__(self):
        return wl.pformat(self, width=80)

    def __add__(self, other):
        return BinaryOperation("+", self, other)


class Symbol(AbstractExpression):
    def __init__(self, name: str):
        self.name = name

    def __call__(self, *args):
        return FunctionCall(self.name, *args)

    def __pdoc__(self, **kwargs):
        return wl.TextDoc(wl.ansi_format(self.name, "red", bold=False))


class BinaryOperation(AbstractExpression):
    def __init__(self, op: str, left: AbstractExpression, right: AbstractExpression):
        self.op = op
        self.left = left
        self.right = right

    def __pdoc__(self, **kwargs):
        left = wl.pdoc(self.left)
        brk = wl.BreakDoc(" ")
        op = wl.TextDoc(self.op)
        right = wl.pdoc(self.right)
        return wl.ConcatDoc(left, brk, op, wl.TextDoc(" "), right)


class FunctionCall(AbstractExpression):
    def __init__(self, fn: str, *args: AbstractExpression):
        self.fn = fn
        self.args = args

    def __pdoc__(self, **kwargs):
        fn = wl.TextDoc(wl.ansi_format(self.fn, "blue", bold=True))
        brk = wl.BreakDoc("")
        args = [wl.pdoc(arg) for arg in self.args]
        args = wl.join(wl.comma, args)
        args = wl.NestDoc(wl.ConcatDoc(brk, wl.GroupDoc(args)), indent=kwargs["indent"])
        return wl.ConcatDoc(fn, wl.TextDoc("("), args, brk, wl.TextDoc(")"))
f = Symbol("amazing_fn")
x = Symbol("arg0")
y = Symbol("arg1")
z = Symbol("arg2")
w = Symbol("arg3")
expr = f(x, y, z + w)

Now let's pretty-print the result:

wl.pprint(expr, width=50)
wl.pprint(expr, width=30)
wl.pprint(expr, width=20)
wl.pprint(expr, width=10)
amazing_fn(arg0, arg1, arg2 + arg3)
amazing_fn(
  arg0, arg1, arg2 + arg3
)
amazing_fn(
  arg0,
  arg1,
  arg2 + arg3
)
amazing_fn(
  arg0,
  arg1,
  arg2
  + arg3
)

And because we defined our __repr__ in terms of pretty-printing, then this works too:

print(repr(expr))
amazing_fn(arg0, arg1, arg2 + arg3)

This is one of the main use-cases for the wadler_lindig library: to define useful, well-formatted reprs for complicated types.

Now let's chase through how this example was constructed. At the top level, we called wadler_lindig.pprint (or wadler_lindig.pformat). This called the __pdoc__ method of the top-level object (a FunctionCall), and formatted the result. Inside that __pdoc__ method, we used wadler_lindig.pdoc to get the pretty-documents of its components, which we then compose together.

Finally as you can see, we've also taken the opportunity to show off our ability to use ANSI colours!


Next example: take a look at the methods example for how to elegantly construct nested structures of documents.