| uip | title | description | author | status | type | category | created |
|---|---|---|---|---|---|---|---|
0119 |
Pretty Printer Improvements |
Make the Hoon pretty printer robust and customizable |
~sidnym-ladrut (@sidnym-ladrut), ~fidwed-sipwyn |
Approved |
Standards Track |
Hoon |
2024-01-20 |
The Hoon pretty printer (i.e. standard library 5c) is broadly
responsible for transforming $type and $vase nouns into $tank
equivalents, generally for the purpose of rendering these nouns in a
human-legible format. While its current implementation admirably performs this
task for nearly all simple and semi-complex nouns, it suffers from performance
and legibility problems for common nontrivial inputs such as meta-$vase
nouns, %lull types, and noun comparisons (e.g. via +dext:nest:ut).
Additionally, the pretty printer's limited set of customization options forces
all output to exist at one of two levels of abstraction: extremely verbose (the
default) or extremely terse (for types annotated with $+). This proposal
contains an array of suggestions to address these deficiencies in an iterative
and user-oriented manner.
This UIP has been approved and has an open PR which can be found here.
There are numerous simple pretty printer invocations that render a ship unusable or produce largely illegible results. The following is a short list of common representative examples:
!>(*),+<..: Processes for several minutes before ultimately crashing the ship with arecover: dig: memeerror.->:!>(*noun): Fails to print any output, exiting with adojo: failed to process inputerror.^-((unit dome:clay) *dome:clay): Produces nearly 900 lines of output for a difference spanning a single line.
The lack of per-type printing customization also makes it difficult to tune the pretty printer's level of detail to the user's operative level of abstraction. This presents problems most commonly for developers at both ends of the abstraction range (i.e. core and app developers), who encounter troubles with outputs for nouns like:
-:!>(*peer-state:ames): Prints#t/#peer-state, which is unhelpful for a core developer attempting to debug/inspect this structure at a nontrivial level of detail.^-((tree [@t @]) (malt ~[[';' 1] [':' 2] ['!' 3]])): Prints the tree using set-like{…}syntax, which makes it difficult for a core developer that wants to see the node ordering of the underlying tree structure.mint:ut: Prints the+mintarm using the pretty printer's<X.abc …>summary syntax, which is difficult to parse for an app developer trying to determine the function's signature (doccords helps with this somewhat, but cannot easily print door arms).^-(hoon 1): Prints:instead of:-need ?( [p=#1 q=#1] ... rest of the hoon type here ... [%sggr p=?([p=@tas q=#1] @tas) q=#1] [%sgwt p=@ud q=#1 r=#1 s=#1] [%sgcb p=#1 q=#1] [%sgbr p=#1 q=^#1.#hoon] ) -have.@ud-need.#hoon :: $hoon is named with $+ -have.@ud
Joe (
~master-morzod) had some interesting ideas for how to rewrite the pretty printer in a way that might be easier—and maybe even more principled—involving using the%hinttype for types, treating that like a mark, and then allowing you to write a function that describes how data of that mark could be pretty printed. Then, you could write a pretty printer where the logic of the printer itself doesn't know anything about those (marks) and is a very dumb genric—and that could be really nice.– Ted Blackman (
~rovnys-ricfer), "Developer Week: Core Dev AMA (2022)"
- Rewrite and replace the
+usdoor to leverage the existing[%hint [%know mark=@tas] …]type structure to enable%mark-like type identification and corresponding custom printing functions.-
The
+usdoor new sample will consist of a maximum depthdep, a verbosity settingveband a pretty printer mappin:++ us => |% +$ tase (each type vase) :: type/vase +$ meta :: recursion metadata $~ [~ ~ 30 %base ~] :: $: saw=(set tase) :: recursive types ids=(map type @) :: types id dep=@ud :: maximum depth veb=?(%base %most %lest) :: default verbosity pin=(map term ppin) :: print overrides == :: +$ base $-([tase meta] (unit [meta tank])) :: base printer +$ ppin $-([tase meta base] (unit [meta tank])) :: custom printer -- :: |% :: … arms start here …
-
depwill be decremented each time that a%cellis found, until it reaches zero, at which point printing will stop with[...]. The maximum depth was primarily introduced to prevent the common issue of the PP hanging for minutes and even causing out of memory errors, observed when trying to print very large outputs (e.g. cores as raw nouns). -
vebis the verbosity setting used by the pretty printer and has three flavors:
%base %most %lest (map @tas @ud) ?(%~ [n=[p=@t q=@ud] l=[...]]) #t/#map u(1) [~ u=1] u(1) {[p=a q=1] [p=b q=2]} [n=[p='b' q=2] l=~ r=[...]] {[a 1] [b 2]} %base: Search for a custom printer (ppin), if not found, fallback to the default printer (hardcoded). If the latter is not present, perform name substitution (similar to%lest).%mostIgnore hints and prints a more extensive output.%lestFor types: Aggressive%knowname substitution for all named types (+$). For vases: Hide all faces. Shorter core printing on bothtypeandvaseprinting.pinenumerates a set of type marks and associated printing functions that will be called for the types named with+$and for all the cases of$type.- The pretty printer gate
$ppinhas the following signature:$-([tase meta base] (unit [meta tank])).- Where
$taseis the input to be printed:(each type vase). $metais used to pass some metadata down into the recursion.- The
baseargument is the base pretty printer gate that will be used by+us, which is passed to each$ppinin order to enable recursive$tankbuilding. - The output is wrapped in a unit because sometimes we return ~ on
error, allowing the
%forkprinter can continue to the next case of the%fork.
- Where
- The following is a rough sketch of a
$ppingate for the$unittype:|= [inp=tase sen=meta bas=base] ^- (unit [meta tank]) =+ typ=?-(-.inp %& p.inp, %| p.p.inp) ?> ?=([%fork *] typ) =+ yed=(sort ~(tap in p.typ) aor) ?> ?=([* [[%cell * [%face *]] ~]] yed) ?- -.inp %& :: type ?~ res=(bas [%& +<+>+>.yed] sen) ~ `[-.u.res [%rose [" " "u(" ")"] +.u.res ~]] %| :: vase (bas inp sen) ==
- The pretty printer gate
-
-
We introduce $+ hints for commonly used types, such as
$set,$map, and$unit, among others. -
Create a new gate
+doxxwith signature$-(type type)thats used internaly by the PP to heuristically annotate the input with%knownotes for the%listtypes (e.g.tape,path,wall, etc.). -
A new generator
:dojo|pp-configwas introduced, allowing the dev to update%dojo's PP config. -
The +nest arm has been updated to avoid throwing unnecessary hints (as mentioned with ^-(hoon 1) above). It also now repeats the error location if the output exceeds an arbitrary size of 100 lines (80 characters each).
-
Several default printers have been reworked, including the gate printing, which changed its style from
<number-of-arms.hash ...subject>to$-(input output).
-
- The fixed PP logic is simple and consists of recursively traversing the type/vase, and for each case of $type (%atom, %cell, %core, etc.), calling the associated printer functions, all of which can technically be overridden by a $ppin gate. The dep makes it flexible to constrain outputs on demand, while veb allows for general customization. Together, these changes make the pretty printer robust and customizable.
- Replacing the old pretty printer will break backwards compatibility with any
code that directly calls the pretty printer's door. However, it will not
affect code that uses functions such as
+sell/+skol/+duck:ut/+dunk:ut. It may also break code that relies on specific pretty printer outputs.
- The
$tanktype could be modified or replaced to be more expressive. - A good noun diff algorithm could be used to make
test.hoon's diffs smaller.
~master-morzod(i.e. @joemfb) for providing architectural sketches.~rovnys-ricfer(i.e. @belisarius222) for motivating this project.~master-morzod(i.e. @joemfb),~rovnys-ricfer(i.e. @belisarius222), and~datnut-pollen(i.e. @drbeefsupreme) for providing a wealth of prior art and examples in%/lib/xray/hoon,%/lib/language-server/easy-print/hoon, and%/lib/dprint/hoon(respectively).
Copyright and related rights waived via CC0.