Skip to content

Latest commit

 

History

History
220 lines (190 loc) · 10.1 KB

UIP-0119.md

File metadata and controls

220 lines (190 loc) · 10.1 KB
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
Final
Standards Track
Hoon
2024-01-20

Abstract

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.

Motivation

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:

  1. !>(*), +<..: Processes for several minutes before ultimately crashing the ship with a recover: dig: meme error.
  2. ->:!>(*noun): Fails to print any output, exiting with a dojo: failed to process input error.
  3. ^-((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:

  1. -:!>(*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.
  2. ^-((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.
  3. mint:ut: Prints the +mint arm 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).
  4. ^-(hoon 1): Prints:
      -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
    instead of:
      -need.#hoon  ::  $hoon is named with $+
      -have.@ud

Specification

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 %hint type 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 +us door to leverage the existing [%hint [%know mark=@tas] …] type structure to enable %mark-like type identification and corresponding custom printing functions.
    • The +us door new sample will consist of a maximum depth dep, a verbosity setting veb and a pretty printer map pin:

      ++  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 …
      • dep will be decremented each time that a %cell is 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).

      • veb is 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). %most Ignore hints and prints a more extensive output. %lest For types: Aggressive %know name substitution for all named types (+$). For vases: Hide all faces. Shorter core printing on both type and vase printing.

      • pin enumerates 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 $ppin has the following signature: $-([tase meta base] (unit [meta tank])).
          • Where $tase is the input to be printed: (each type vase).
          • $meta is used to pass some metadata down into the recursion.
          • The base argument is the base pretty printer gate that will be used by +us, which is passed to each $ppin in order to enable recursive $tank building.
          • The output is wrapped in a unit because sometimes we return ~ on error, allowing the %fork printer can continue to the next case of the %fork.
        • The following is a rough sketch of a $ppin gate for the $unit type:
          |=  [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)
          ==
    • We introduce $+ hints for commonly used types, such as $set, $map, and $unit, among others.

    • Create a new gate +doxx with signature $-(type type) thats used internaly by the PP to heuristically annotate the input with %know notes for the %list types (e.g. tape, path, wall, etc.).

    • A new generator :dojo|pp-config was 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).

Rationale

  • 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.

Backwards Compatibility

  • 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.

Future work

  • The $tank type could be modified or replaced to be more expressive.
  • A good noun diff algorithm could be used to make test.hoon's diffs smaller.

Acknowledgements

  • ~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

Copyright and related rights waived via CC0.