From 3dacb2c228d386efea3f1956eb32c30f38b98e72 Mon Sep 17 00:00:00 2001 From: Sigilante Date: Tue, 4 Aug 2026 13:39:45 -0600 Subject: [PATCH 1/2] docs: document undocumented Hoon features (^_, +vi, bit ops, +slub, date padding) Additions and corrections against urbit@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 --- content/hoon/rune/ket.md | 76 ++++++++++++++++++- content/hoon/rune/tis.md | 14 ++++ content/hoon/stdlib/2c.md | 149 ++++++++++++++++++++++++++++++++++++++ content/hoon/stdlib/4k.md | 18 ++++- content/hoon/stdlib/4n.md | 76 ++++++++++++++++++- content/hoon/stdlib/5c.md | 50 +++++++++++++ 6 files changed, 376 insertions(+), 7 deletions(-) diff --git a/content/hoon/rune/ket.md b/content/hoon/rune/ket.md index 18346b7d..7e779314 100644 --- a/content/hoon/rune/ket.md +++ b/content/hoon/rune/ket.md @@ -15,10 +15,84 @@ layout: # ^ ket ยท Casts -[`^-` ("kethep")](#--kethep), [`^+` ("ketlus")](#-ketlus), and [`^=` ("kettis")](#-kettis) let us adjust types without violating type constraints. +[`^-` ("kethep")](#kethep), [`^+` ("ketlus")](#ketlus), and [`^=` ("kettis")](#kettis) let us adjust types without violating type constraints. The `+nest` algorithm which tests subtyping is conservative; it never allows invalid nests, it sometimes rejects valid nests. +## ^_ "ketcab" {#ketcab} + +Assert that an expression nests in a type, without casting it. + +#### Syntax + +Two arguments, fixed. + +{% tabs %} + +{% tab title="Tall form" %} + +```hoon +^_ p +q +``` + +{% endtab %} + +{% tab title="Wide form" %} + +```hoon +^_(p q) +``` + +{% endtab %} + +{% tab title="Irregular form" %} + +None + +{% endtab %} + +{% endtabs %} + +#### AST + +```hoon +[%ktcb p=hoon q=hoon] +``` + +#### Produces + +The product of `.q`, with **`.q`'s own type** โ€” not `.p`'s. + +#### Discussion + +`^_` compiles `.q` using `.p`'s type as the goal, so `.q` must nest in `.p`'s +type or the expression fails to compile. Unlike [`^-`](#kethep), however, the +result keeps the type inferred for `.q`. It is a type *test* rather than a cast. + +The difference is visible in the dojo: + +``` +> ? ^_(*@ 'a') + @t +'a' + +> ? ^-(@ 'a') + @ +97 +``` + +Both accept `'a'`, because `@t` nests in `@`. `^-` casts the result to `@`, so it +prints as `97`; `^_` leaves it as `@t`, so it prints as `'a'`. + +`^_` is what [`=^`](tis.md#tisket) desugars through, which is why that rune +requires its right-hand expression to produce a cell. + +Note this rune was spelled `^#` until June 2026; on a kernel from before that +change, use `^#` instead. + +--- + ## ^| "ketbar" {#ketbar} Convert a gold core to an iron core (contravariant). diff --git a/content/hoon/rune/tis.md b/content/hoon/rune/tis.md index 0e36fc84..eedd8efc 100644 --- a/content/hoon/rune/tis.md +++ b/content/hoon/rune/tis.md @@ -513,6 +513,20 @@ This may also remind you of Haskell's State monad. Note that `=^` is subject to the same type nesting limitations as `=.`; e.g., if you have `?~` checked a list for null, you can no longer nest a regular list in the result. (In this case, use a nock `=(~ ...)` equality check instead or recast the result.) +`.r` **must produce a cell.** Since 2026, `=^` desugars through +[`^_`](ket.md#ketcab) with a cell as the goal, so a non-cell right-hand side is a +compile error rather than something that slides through: + +``` +> =| a=@ =^ b a [1 2] [b a] +[1 2] + +> =| a=@ =^ b a 5 [b a] +-need.[* *] +-have.@ud +nest-fail +``` + #### Examples The `+og` core is a stateful pseudo-random number generator. We have to change the core state every time we generate a random number, so we use `=^`: diff --git a/content/hoon/stdlib/2c.md b/content/hoon/stdlib/2c.md index 345305a7..fa13e58c 100644 --- a/content/hoon/stdlib/2c.md +++ b/content/hoon/stdlib/2c.md @@ -213,6 +213,75 @@ An `$atom`. *** +## `+clz` {#clz} + +Leading zeros. + +Counts the leading zero bits of `.b` within the block width given by `.a`. + +#### Accepts + +`.a` is a [`$bite`](1c.md#bite). + +`.b` is an `$atom`. + +#### Produces + +An `$atom`. + +#### Source + +```hoon +++ clz + ~/ %clz + |= [a=bite b=@] + =/ c=[=bloq =step] ?^(a a [a 1]) + (sub (mul (bex bloq.c) step.c) (met 0 (end a b))) +``` + +#### Examples + +``` +> (clz 3 8) +4 +``` + +--- + +## `+ctz` {#ctz} + +Trailing zeros. + +Counts the trailing zero bits of `.a`. Produces `0` for an input of `0`. + +#### Accepts + +`.a` is an `$atom`. + +#### Produces + +An `$atom`. + +#### Source + +```hoon +++ ctz + ~/ %ctz + |= a=@ + ?: =(0 a) 0 + =| i=@ud + |-(?:(=(1 (cut 0 [i 1] a)) i $(i +(i)))) +``` + +#### Examples + +``` +> (ctz 8) +3 +``` + +--- + ## `+cut` {#cut} Slice. @@ -438,6 +507,44 @@ An `$atom`. *** +## `+ham` {#ham} + +Population count. + +Counts the set bits of `.a` โ€” the Hamming weight, or popcount. + +#### Accepts + +`.a` is an `$atom`. + +#### Produces + +An `$atom`. + +#### Source + +```hoon +++ ham + ~/ %ham + |= a=@ + ?: =(0 a) 0 + =| n=@ud + =/ m (dec (met 0 a)) + |- ^- @ud + =? n =(1 (cut 0 [m 1] a)) + +(n) + ?:(=(0 m) n $(m (dec m))) +``` + +#### Examples + +``` +> (ham 255) +8 +``` + +--- + ## `+lsh` {#lsh} Left-shift. @@ -797,6 +904,48 @@ An `$atom`. *** +## `+rig` {#rig} + +Convert block sizes. + +Re-expresses the step of [`$bite`](1c.md#bite) `.bite` in terms of block size `.b`. + +#### Accepts + +`.bite` is a [`$bite`](1c.md#bite). + +`.b` is a [`$bloq`](1c.md#bloq). + +#### Produces + +A `+step`. + +#### Source + +```hoon +++ rig + ~/ %rig + |= [=bite b=bloq] + ^- step + ?@ bite 0 + =/ [a=bloq c=step] bite + ?: =(a b) c + ?: (gth a b) + (lsh [0 (sub a b)] c) + =/ d [0 (sub b a)] + =/ e (rsh d c) + ?:(=(0 (end d c)) e +(e)) +``` + +#### Examples + +``` +> (rig [3 2] 4) +1 +``` + +--- + ## `+rip` {#rip} Disassemble. diff --git a/content/hoon/stdlib/4k.md b/content/hoon/stdlib/4k.md index 0e462833..eec4ac2f 100644 --- a/content/hoon/stdlib/4k.md +++ b/content/hoon/stdlib/4k.md @@ -126,6 +126,20 @@ Render as `$tape`. Renders a `$coin` `.lot` as a `$tape`. +> **Date rendering changed in 2026 (UIP-135).** The month, day, hour, minute and +> second of a `@da`/`@d` are now zero-padded to two digits with `+y-co`, where +> they were previously rendered unpadded with `+a-co`. This changes the *textual* +> output of anything that renders a date, including `(scot %da now)`: +> +> ``` +> > (scot %da ~2024.1.2..03.04.05..0006) +> ~.~2024.01.02..03.04.05..0006 +> ``` +> +> Parsing accepts both forms, so round-tripping is unaffected โ€” but code that +> string-compares rendered dates, or parses them with a strict pattern, may need +> updating. + #### Accepts `.lot` is a `$coin`, and is the sample of `+co`. @@ -161,8 +175,8 @@ A `$tape`. =. rep ['.' (y-co s.t.yod)] =. rep ['.' (y-co m.t.yod)] ['.' '.' (y-co h.t.yod)] - =. rep ['.' (a-co d.t.yod)] - =. rep ['.' (a-co m.yod)] + =. rep ['.' (y-co d.t.yod)] + =. rep ['.' (y-co m.yod)] =? rep !a.yod ['-' rep] ['~' (a-co y.yod)] :: diff --git a/content/hoon/stdlib/4n.md b/content/hoon/stdlib/4n.md index 9a494ef6..7fab586b 100644 --- a/content/hoon/stdlib/4n.md +++ b/content/hoon/stdlib/4n.md @@ -470,6 +470,56 @@ A `$toon`. --- +## `+vi` {#vi} + +Virtualization door. + +Container door for the virtualization arms [`+mole`](#mole), [`+mule`](#mule), +[`+mure`](#mure) and [`+mute`](#mute). Its sample `.for` controls whether a +scry (Nock 12) performed inside the virtualized computation is forwarded to the +real namespace. + +#### Accepts + +`.for` is a `?`. + +#### Source + +```hoon +++ vi + ~% %vi ..vi + == + %mure mure + %mute mute + == + :: forward namespace? + :: + |_ for=? +``` + +#### Discussion + +When `.for` is true, the arms build a scry handler and pass it to +[`+mink`](#mink)/[`+mock`](#mock); when it is false they pass `~`, the "no scry +handler" sentinel, so any Nock 12 inside blocks and the computation reports +failure. + +**The default forwards.** `.for` is a `?`, whose bunt is `%.y`, so `mure:vi` and +the bare aliases forward scries. Pass `|` explicitly to prevent that: + +``` +> (mure:vi |.(.^(? %cx /=//=/esse/base))) +[~ 0] + +> (~(mure vi |) |.(.^(? %cx /=//=/esse/base))) +~ +``` + +Only `+mole` and `+mule` have top-level aliases (`++ mole mole:vi`, +`++ mule mule:vi`). `+mure` and `+mute` must be reached through the door. + +--- + ## `+mole` {#mole} Typed unitary virtual. @@ -617,12 +667,16 @@ An `(each * (list tank))` - `%.y` indicates success and `%.n` indicates a crash. --- -## `+mure` {#mure} +## `+mure:vi` {#mure} Untyped unitary virtual. Kicks a `+trap`, producing its results in a `+unit` which is null if it crashed. The result is just a `$noun`, no type information is preserved. +> **Note.** `+mure` is no longer a top-level arm. It lives in the +> [`+vi`](#vi) door, and a bare `(mure ...)` fails with `-find.mure`. Use +> `mure:vi`, or `~(mure vi for)` to control namespace forwarding. + #### Accepts A `+trap`. @@ -637,7 +691,12 @@ A `(unit *)`. ++ mure |= tap=(trap) ^- (unit) - =/ ton (mink [tap %9 2 %0 1] |=((pair) ``.*(~ [%12 1+p 1+q]))) + =/ gul + ?. for ~ + => ~ + |=(a=^ ``.*(a 12+[0+2 0+3])) + :: + =/ ton (mink [tap %9 2 %0 1] gul) ?.(?=(%0 -.ton) ~ `product.ton) ``` @@ -665,12 +724,16 @@ A `(unit *)`. --- -## `+mute` {#mute} +## `+mute:vi` {#mute} Untyped virtual. Kicks a `+trap`, producing its result as a `$noun` or the tanks of any error that occurs. Similar to [`+mule`](#mule), but preserves no type information. +> **Note.** `+mute` is no longer a top-level arm. It lives in the +> [`+vi`](#vi) door, and a bare `(mute ...)` fails with `-find.mute`. Use +> `mute:vi`, or `~(mute vi for)` to control namespace forwarding. + #### Accepts `+tap` is a `+trap`. @@ -685,7 +748,12 @@ A `(each * (list tank))`, where `%.y` indicates success and `%.n` indicates fail ++ mute |= tap=(trap) ^- (each * (list tank)) - =/ ton (mock [tap %9 2 %0 1] |=((pair) ``.*(~ [%12 1+p 1+q]))) + =/ gul + ?. for ~ + => ~ + |=(a=^ ``.*(a 12+[0+2 0+3])) + :: + =/ ton (mock [tap %9 2 %0 1] gul) ?- -.ton %0 [%& p.ton] :: diff --git a/content/hoon/stdlib/5c.md b/content/hoon/stdlib/5c.md index b9906da4..f1cc794c 100644 --- a/content/hoon/stdlib/5c.md +++ b/content/hoon/stdlib/5c.md @@ -458,6 +458,56 @@ A `$vase`. --- +## `+slub` {#slub} + +Memoized [`+slap`](#slap). + +Compiles hoon `.gen` with subject `.vax`, producing a `$vase` of the result, and +caches the compilation. + +#### Accepts + +`.vax` is a `$noun` in a `$vase`, and is the subject. + +`.gen` is some `$hoon`. + +#### Produces + +A `$vase`. + +#### Source + +```hoon +++ slub + |= [vax=vase gen=hoon] ^- vase + =/ gun + => [vax=p=p.vax gen=gen ut=ut] + ~> %memo./hoon/mint + (~(mint ut p.vax) %noun gen) + [p.gun .*(q.vax q.gun)] +``` + +#### Examples + +``` +> (slub !>(b='foo') (ream '|=(a=@t [a b])')) +[#t/<1.jtd [a=@t b=@t]> q=[[[0 6] 0 7] 0 7.303.014]] +``` + +#### Discussion + +`+slub` produces the same result as [`+slap`](#slap), but wraps the `+mint` call +in a `~> %memo./hoon/mint` hint. The runtime therefore caches the compilation +keyed on `/hoon/mint`, so repeating the same compile is cheap. + +Note that this cache is a side effect with process lifetime: it is held by the +runtime, not by the caller, and is not reflected in the product. `+slub` is worth +preferring over `+slap` where the same expression is compiled repeatedly in a hot +path. The cache can be dropped with the `%drop` runtime hint or by a `|meld` +with `=ford &`. + +--- + ## `+slog` {#slog} Deify printf. From d58aee8aee20761578980f88061fe7f3e8595a67 Mon Sep 17 00:00:00 2001 From: Sigilante Date: Tue, 4 Aug 2026 15:06:24 -0600 Subject: [PATCH 2/2] docs: add +hew to the bit-arithmetic reference 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 --- content/hoon/stdlib/2c.md | 55 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/content/hoon/stdlib/2c.md b/content/hoon/stdlib/2c.md index fa13e58c..3fe341b8 100644 --- a/content/hoon/stdlib/2c.md +++ b/content/hoon/stdlib/2c.md @@ -545,6 +545,61 @@ An `$atom`. --- +## `+hew` {#hew} + +Cut many. + +Cuts several values out of an atom in sequence, shaped by the argument you apply +the resulting gate to, and reports where it left off. + +#### Accepts + +`.a` is a [`$bite`](1c.md#bite), the block size and starting offset. + +`.c` is an `$atom`, the source. + +The product is a wet gate; apply it to a *shape* describing how many blocks each +field should take. + +#### Produces + +A cell of the filled-in shape and the new `[bloq step]` position. + +#### Source + +```hoon +++ hew + ~/ %hew + |= [a=bite c=@] + =/ d=[=bloq =step] ?^(a a [a 0]) + ~% %fun +>+ ~ + |* b=* + ^+ [b d] + ?@ b + [(cut bloq.d [step.d b] c) bloq.d (add step.d b)] + =^ f d $(b -.b) + =^ g d $(b +.b) + [[f g] d] +``` + +#### Examples + +Cutting two blocks as one value: + +``` +> ((hew [3 0] 0xdead.beef) 2) +[48.879 bloq=3 step=2] +``` + +Cutting two single blocks into a cell: + +``` +> ((hew [3 0] 0xdead.beef) [1 1]) +[[239 190] bloq=3 step=2] +``` + +--- + ## `+lsh` {#lsh} Left-shift.