Skip to content

docs: document undocumented Hoon features (^_, +vi, bit ops, +slub, date padding) - #272

Open
sigilante wants to merge 2 commits into
urbit:masterfrom
sigilante:docs/pr11-hoon-features
Open

docs: document undocumented Hoon features (^_, +vi, bit ops, +slub, date padding)#272
sigilante wants to merge 2 commits into
urbit:masterfrom
sigilante:docs/pr11-hoon-features

Conversation

@sigilante

Copy link
Copy Markdown
Contributor

Seventh PR from the audit against urbit/urbit@08026c84b2. Features that shipped without documentation, plus one change that silently alters rendered output. Every claim reproduced on a fake ship (urbit-408k-rc1.pill) except where noted.

Companion PRs: #266, #267, #268, #269, #270, #271.

^_ "ketcab" — new rune section

[%ktcb p=hoon q=hoon], parsed at hoon.hoon:13213, commented :: ^_ test. It mints .q with .p's type as the goal but produces .q's own type — an assertion of nesting, not a cast:

> ? ^_(*@ 'a')
  @t
'a'

> ? ^-(@ 'a')
  @
97

A caveat on how this was verified. The rune was renamed from ^# to ^_ on 2026-06-18 (7e4610fde2), and the 408 rc1 pill predates that — the ship accepts ^# and not ^_. The semantics above were verified on the ship using ^#; the glyph is taken from develop, which parses %ktcb only from '_'. The doc notes the old spelling for anyone on an earlier 408 build.

=^ now requires a cell

Since b47362a193 (2026-02-10), =^ desugars through ^_ with a cell goal (hoon.hoon:8667), so a non-cell right-hand side is a compile error rather than something that slides through:

> =|  a=@  =^  b  a  5  [b a]
-need.[* *]
-have.@ud
nest-fail

+vi — new section, and +mure/+mute retargeted

Bare +mure and +mute no longer exist: (mure |.(42)) gives -find.mure, while (mure:vi |.(42)) gives [~ 42]. Only +mole and +mule have top-level aliases (hoon.hoon:6334-6335). Both source blocks were stale and are regenerated.

One correction to the audit worth flagging. It claimed the door's bunt is |, so the bare aliases do not forward scries. That is backwards — .for is a ?, whose bunt is %.y, so the default does forward:

> *?                                              %.y
> (mure:vi |.(.^(? %cx /=//=/esse/base)))         [~ 0]
> (~(mure vi |) |.(.^(? %cx /=//=/esse/base)))    ~

Documenting it the other way round would have told readers the opposite of the truth.

Date rendering changed (UIP-135)

+rend:co now pads month and day with +y-co (= (d-co 2)) where the doc showed +a-co (= (d-co 1)). This changes the textual output of every rendered date, including (scot %da now):

> (scot %da ~2024.1.2..03.04.05..0006)
~.~2024.01.02..03.04.05..0006

Parsing accepts both padded and unpadded forms and round-trips cleanly (verified), so the hazard is limited to code that string-compares or strictly parses rendered dates. Noted as such rather than overstated.

Bit operations — four new sections

+clz, +ctz, +ham, +rig. All jetted, all previously undocumented.

> [(clz 3 8) (ctz 8) (ham 255)]
[4 3 8]

> (rig [3 2] 4)
1

Each source block was then checked line-by-line against sys/hoon.hoon by script — which caught a real error: I had written +rig's tail as a nested ?: over two (rsh d c) calls, where the source binds =/ e (rsh d c) and produces ?:(=(0 (end d c)) e +(e)). Corrected before commit.

+hew is deliberately not added: it is a wet gate with a calling convention I could not demonstrate correctly, and a wrong example is worse than none.

+slub — new section

A memoized +slap (hoon.hoon:11445) wrapping +mint in ~> %memo./hoon/mint. The cache is documented explicitly as a process-lifetime side effect held by the runtime — invisible in the signature — and noted as droppable via the %drop hint or |meld, =ford &.

Incidental

Fixed three pre-existing broken anchor links in ket.md's intro (#--kethep, #-ketlus, #-kettis#kethep, #ketlus, #kettis).

All anchors in the touched files verified to resolve. Test-merged against the six open companion PRs — all clean.

🤖 Generated with Claude Code

…ate padding)

Additions and corrections against urbit/docs.urbit.org@08026c84b2. Every claim was reproduced on
a fake ship booted from urbit-408k-rc1.pill, except where noted.

^_ "ketcab" (rune/ket.md) -- NEW SECTION. `[%ktcb p=hoon q=hoon]`, parsed at
  hoon.hoon:13213 and commented `::  ^_  test`. It mints .q with .p's type as
  the goal but produces .q's OWN type, so it asserts nesting without casting:
    > ? ^_(*@ 'a')      ->  @t / 'a'
    > ? ^-(@ 'a')       ->  @  / 97
  CAVEAT ON VERIFICATION: the rune was renamed from ^# to ^_ on 2026-06-18
  (7e4610fde2), and the 408 rc1 pill predates that, so the ship accepts ^# and
  not ^_. The SEMANTICS above were verified on the ship using ^#; the GLYPH is
  taken from develop, which parses %ktcb only from '_'. The doc notes the old
  spelling for anyone on an earlier 408 build.

=^ (rune/tis.md): documented that .r must produce a cell. Since b47362a193
  (2026-02-10) =^ desugars through ^_ with a cell goal (hoon.hoon:8667), so a
  non-cell right-hand side is now a compile error:
    > =|  a=@  =^  b  a  5  [b a]
    -need.[* *] / -have.@ud / nest-fail

+vi (stdlib/4n.md) -- NEW SECTION, and +mure/+mute retargeted.
  Bare +mure and +mute NO LONGER EXIST: `(mure |.(42))` gives -find.mure, while
  `(mure:vi |.(42))` gives [~ 42]. Only +mole and +mule have top-level aliases
  (hoon.hoon:6334-6335). Both source blocks were stale and are regenerated.

  CORRECTION TO THE AUDIT: it claimed the door's bunt is `|`, so the bare
  aliases do not forward scries. That is backwards. `.for` is a `?`, whose bunt
  is %.y, so the default DOES forward. Verified:
    > *?                                                    ->  %.y
    > (mure:vi |.(.^(? %cx /=//=/esse/base)))               ->  [~ 0]
    > (~(mure vi |) |.(.^(? %cx /=//=/esse/base)))          ->  ~
  Documenting it the other way round would have told readers the opposite of
  the truth.

Date rendering (stdlib/4k.md): +rend:co now pads month and day with +y-co
  (= (d-co 2)) where the doc showed +a-co (= (d-co 1)). This changes the
  TEXTUAL output of every rendered date, including (scot %da now):
    > (scot %da ~2024.1.2..03.04.05..0006)
    ~.~2024.01.02..03.04.05..0006
  Parsing accepts both padded and unpadded forms and round-trips cleanly
  (verified), so the hazard is limited to code that string-compares or strictly
  parses rendered dates. Noted as such rather than overstated.

Bit operations (stdlib/2c.md) -- FOUR NEW SECTIONS: +clz, +ctz, +ham, +rig.
  All jetted, all previously undocumented. Verified:
    > [(clz 3 8) (ctz 8) (ham 255)]  ->  [4 3 8]
    > (rig [3 2] 4)                  ->  1
  Every source block was then checked line-by-line against sys/hoon.hoon by
  script -- which caught a real error: I had written +rig's tail as a nested
  ?: over two (rsh d c) calls, where the source binds `=/  e  (rsh d c)` and
  produces `?:(=(0 (end d c)) e +(e))`. Corrected before commit.
  +hew is deliberately NOT added: it is a wet gate with a calling convention I
  could not demonstrate correctly, and a wrong example is worse than none.

+slub (stdlib/5c.md) -- NEW SECTION. A memoized +slap (hoon.hoon:11445) that
  wraps +mint in `~>  %memo./hoon/mint`. Documented the cache explicitly as a
  process-lifetime side effect held by the runtime, since that is invisible in
  the signature, and noted it can be dropped via the %drop hint or `|meld,
  =ford &`.

Incidental: fixed three pre-existing broken anchor links in ket.md's intro
(#--kethep, #-ketlus, #-kettis -> #kethep, #ketlus, #kettis).

All anchors in the touched files verified to resolve.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Follow-up on this branch. +hew (hoon.hoon:812) was deliberately omitted from the
earlier commit because I could not demonstrate its calling convention and a wrong
example is worse than none. Worked it out and verified it:

  ((hew [3 0] 0xdead.beef) 2)      ->  [48.879 bloq=3 step=2]
  ((hew [3 0] 0xdead.beef) [1 1])  ->  [[239 190] bloq=3 step=2]

The gate produced by (hew bite atom) is applied to a SHAPE, not a count. An atom
means "take this many blocks as one value"; a cell means "fill this structure,
one cut per leaf". Either way it also returns the updated [bloq step], so cuts
can be chained. That is what the earlier attempt got wrong -- I had been passing
it as though it took a plain argument.

Placed here rather than on the typed-paths branch: +hew belongs in 2c.md
immediately before +lsh, which is the same insertion point this branch already
uses for +ham, so the two would have conflicted.

The source block was written from inference on a first pass and was WRONG -- I
had the tail as two =/ bindings threading `d` manually, where the source uses
`=^  f  d  $(b -.b)` twice and produces `[[f g] d]`. Caught by diffing against
hoon.hoon before commit, the same check that caught +rig earlier on this branch.
All five blocks in 2c.md (clz, ctz, ham, hew, rig) now verify as exact matches.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant