hoon-development
Write and compile Hoon code following Nock conventions and NockApp patterns. Use when working with Hoon kernel code, modifying state structures, adding pokes/peeks, or debugging Hoon compilation.
When & Why to Use This Skill
This Claude skill provides a comprehensive framework for writing, compiling, and debugging idiomatic Hoon code tailored for NockApp kernels. It streamlines the development process by assisting with complex state management, immutable data updates, and the implementation of core Nock patterns, making it an essential tool for developers working within the Urbit ecosystem.
Use Cases
- Kernel Development: Writing and structuring idiomatic Hoon code for Prover NockApp kernels following strict Nock conventions.
- State Architecture: Designing and implementing versioned state structures with safe, immutable update patterns to ensure data integrity.
- Input Handling: Implementing the ++poke pattern to manage system commands, validate inputs, and generate appropriate effect lists.
- Troubleshooting: Debugging Hoon-specific compilation errors such as 'mint', 'find', and 'nest' failures through guided code analysis.
- Data Manipulation: Utilizing specialized Hoon map and list operations for efficient data processing within the Nock environment.
| name | hoon-development |
|---|---|
| description | Write and compile Hoon code following Nock conventions and NockApp patterns. Use when working with Hoon kernel code, modifying state structures, adding pokes/peeks, or debugging Hoon compilation. |
Hoon Development Guide
This skill helps you write idiomatic Hoon code for the Prover NockApp kernel.
Hoon Basics
File Structure
:: Comments start with ::
:: Documentation comments
::
|% :: Core definition (library of types and functions)
+$ type-name :: Type definition
structure
--
|_ =state :: Door (object with state)
++ arm-name :: Arm (method/function)
code
--
Type Definitions
:: Atoms (basic types)
@ud :: Unsigned decimal (0, 1, 42)
@t :: Text/cord ('hello')
@tas :: Term/symbol (%groth16, %pending)
@da :: Timestamp/date (~2024.1.1)
:: Complex types
(list @t) :: List of text
(map @ud @t) :: Map from numbers to text
(unit @t) :: Optional text (~ or [~ 'value'])
?(%a %b %c) :: Union of terms
:: Structures
+$ person
$: name=@t
age=@ud
email=@t
==
:: Unions (discriminated)
+$ action
$% [%create name=@t]
[%update id=@ud name=@t]
[%delete id=@ud]
==
State Management
State Structure
Your NockApp state should be versioned:
+$ state
$: %v1 :: Version marker
data=(map @ud entry) :: Your data
next-id=@ud :: ID counter
config=settings :: Configuration
==
Accessing State Fields
:: Read field
=/ current-id next-id.state
:: Read nested field
=/ first-entry (~(get by data.state) 1)
Updating State Immutably
IMPORTANT: Never mutate state directly. Always create a new state.
:: Update single field
state(next-id +(next-id.state))
:: Update multiple fields
state(data new-data, next-id +(next-id.state))
:: Update with complex computation
=/ new-data (~(put by data.state) key value)
=/ new-id +(next-id.state)
state(data new-data, next-id new-id)
The ++poke Pattern
All input commands flow through ++poke:
++ poke
|= =cause :: Take a cause as input
^- [effects=(list effect) _state] :: Return effects and new state
?- -.cause :: Pattern match on cause tag
%command-one
:: Handle command one
:_ state :: Return state first (reversed)
:~ [%http-response 200 body] :: Effect list
==
%command-two
:: Handle command two
=/ updated-state state(...)
:_ updated-state
:~ [%http-response 201 body]
[%log 'Action completed']
==
==
Key Patterns
Extract inputs from cause:
=/ input-field field.causeValidate inputs:
?: =(input-field 0) :_ state [%http-response 400 '{"error":"Field cannot be zero"}']~Compute new values:
=/ new-id +(next-id.state) =/ entry [id data timestamp]Update state:
=/ updated-state state(data (~(put by data.state) key entry))Return effects and state:
:_ updated-state :~ [%http-response 200 body] [%log 'Success'] ==
Map Operations
Common Map Functions
:: Insert or update
=/ new-map (~(put by old-map) key value)
:: Lookup (returns unit)
=/ maybe-value (~(get by map) key)
?~ maybe-value
:: Handle not found
:: Handle found, use u.maybe-value
:: Delete
=/ new-map (~(del by old-map) key)
:: Check existence
=/ exists (~(has by map) key)
:: Convert to list
=/ pairs ~(tap by map) :: List of [key value]
:: Get size
=/ count ~(wyt by map)
Safe Map Access Pattern
=/ maybe-entry (~(get by snarks.state) id.cause)
?~ maybe-entry
:: Not found - return 404
:_ state
:~ [%http-response 404 '{"error":"Not found"}']
==
:: Found - use u.maybe-entry
=/ entry u.maybe-entry
:: ... continue with entry
List Operations
:: Create list
=/ my-list ~['a' 'b' 'c']
=/ empty-list ~
:: Add to front
=/ new-list [item my-list]
:: Iterate/map
=/ doubled (turn my-list |=(x=@ud (mul x 2)))
:: Filter
=/ evens (skim my-list |=(x=@ud =(0 (mod x 2))))
:: Fold/reduce
=/ sum (roll my-list add)
:: Check if empty
?~ my-list
:: Empty case
:: Non-empty case, i.my-list is head, t.my-list is tail
Control Flow
Conditionals
:: If-then-else (for values)
?: condition
value-if-true
value-if-false
:: If-null check
?~ maybe-value
handle-null
handle-value :: u.maybe-value is the unwrapped value
:: Pattern matching
?- value
%option-a result-a
%option-b result-b
%option-c result-c
==
Variable Binding
:: Bind variable
=/ name value
:: Bind with type
=/ name ^- @ud 42
:: Sequential bindings
=/ a 1
=/ b 2
=/ c (add a b)
c :: Returns 3
Text and String Operations
Creating Text
:: Literal cord (compile-time)
'hello'
:: Convert with crip (runtime)
(crip "hello")
:: Convert tape to cord
=/ t "hello"
=/ c (crip t)
Text Formatting
:: Format number to text
=/ num-text (scow %ud 42) :: "42"
=/ id-text (scow %ud id.cause) :: Format ID
:: Convert text to tape for manipulation
=/ tape-form (trip 'hello') :: "hello"
:: Concatenate (as tapes)
=/ result (weld "hello" " world")
=/ cord-result (crip result) :: 'hello world'
JSON-ish String Building (Simplified)
:: Simple response
'{"success":true,"id":42}'
:: With interpolation (manual)
=/ id-str (scow %ud id)
(crip (weld "{\"id\":" (weld id-str "}")))
Note: The prover uses helper functions for JSON formatting. See ++format-submit-response in prover.hoon.
Common Helper Functions
From the Prover Codebase
++ format-submit-response
|= id=@ud
^- tape
(weld "{\"success\":true,\"id\":" (weld (trip (scow %ud id)) "}"))
++ format-snark-detail
|= [id=@ud entry=snark-entry]
^- tape
:: Build JSON response string
...
Creating Your Own Helpers
++ build-error-response
|= message=@t
^- @t
(crip (weld "{\"error\":\"" (weld (trip message) "\"}")))
++ format-list-response
|= items=(list @t)
^- @t
:: Convert list to JSON array
...
Type Hints and Casts
:: Specify return type
=/ value ^- @ud 42
:: Cast to type
=/ entry ^- snark-entry
[id proof inputs vk system submitter now %pending ~ notes]
:: Useful for complex structures
=/ new-state ^- state
state(data new-data, next-id new-id)
The Bowl (Context)
In NockApp pokes, you have access to a bowl with context:
++ poke
|= [=cause =bowl:cask] :: Bowl is available
^- [(list effect:cask) _state]
:: Access bowl fields:
now.bowl :: Current timestamp (@da)
our.bowl :: Our identity
:: etc.
Common use: Get current timestamp
=/ timestamp now.bowl
=/ entry [id data timestamp ...]
Note: In the prover, we use now directly (it's implicitly from bowl).
Error Handling
Validation Pattern
:: Check condition
?: (lth id.cause 1)
:: Return error
:_ state
:~ [%http-response 400 '{"error":"ID must be positive"}']
==
:: Continue with valid input
...
Not-Found Pattern
=/ maybe-entry (~(get by data.state) id.cause)
?~ maybe-entry
:_ state
:~ [%http-response 404 '{"error":"Not found"}']
[%log (crip "Entry {(scow %ud id.cause)} not found")]
==
:: Entry exists, use u.maybe-entry
...
Effects
Common effect types:
[%http-response code=@ud body=@t] :: HTTP response
[%log message=@t] :: Log message
[%error message=@t] :: Error log
Multiple Effects
:_ state
:~ [%http-response 201 body]
[%log 'SNARK submitted']
[%log (crip "ID: {(scow %ud new-id)}")]
==
Compiling and Testing
Compilation
# Compile Hoon to Nock
hoonc prover/hoon/prover.hoon -o prover/out.jam
# Check for syntax errors
hoonc --check prover/hoon/prover.hoon
Common Compilation Errors
Mint failure: Type mismatch
- Check your type annotations (
^- type value) - Ensure structure matches type definition
- Check your type annotations (
Find failure: Unknown name
- Check spelling of variables and arms
- Ensure variables are in scope (
=/binding)
Nest failure: Type doesn't fit expected shape
- Check structure field order
- Verify all required fields are present
Debugging Tips
Add log effects:
[%log 'Reached this point'] [%log (crip "Value: {(scow %ud value)}")]Simplify complex expressions:
:: Instead of nested calls =/ step1 (operation1 input) =/ step2 (operation2 step1) =/ step3 (operation3 step2) step3Check types with hints:
=/ value ^- expected-type expression
Best Practices
- Always version your state:
$: %v1 ... - Use type hints for complex values:
^- type value - Validate inputs early: Check before updating state
- Keep arms focused: One responsibility per arm
- Use helper functions: Extract formatting and validation
- Log important operations: Help with debugging
- Handle errors explicitly: Don't assume success
- Update state immutably: Use
state(field new-value) - Test incrementally: Compile after each change
- Follow existing patterns: Match the style in prover.hoon
Quick Reference
Variable Binding
=/ name value :: Bind variable
=/ name ^- @ud 42 :: With type hint
State Update
state(field value) :: Single field
state(field1 val1, field2 val2) :: Multiple fields
Map Operations
(~(put by map) key value) :: Insert/update
(~(get by map) key) :: Lookup (returns unit)
(~(del by map) key) :: Delete
~(tap by map) :: Convert to list
Conditionals
?: condition true-branch false-branch :: If-then-else
?~ maybe-val null-branch value-branch :: If-null
?- value %a a %b b == :: Pattern match
Effects
:_ state
:~ [%http-response 200 '{"ok":true}']
[%log 'Done']
==
Example: Adding a New Poke
Let's add a %count-snarks command:
:: 1. Add to cause type
+$ cause
$% ...
[%count-snarks ~]
==
:: 2. Implement in ++poke
++ poke
|= [=cause =bowl:cask]
^- [(list effect:cask) _state]
?- -.cause
...
%count-snarks
=/ count ~(wyt by snarks.state)
=/ response (crip (weld "{\"count\":" (weld (trip (scow %ud count)) "}")))
:_ state
:~ [%http-response 200 response]
[%log (crip "Total SNARKs: {(scow %ud count)}")]
==
==
Done! Compile and test:
hoonc prover/hoon/prover.hoon -o prover/out.jam
Additional Resources
- Hoon School: https://developers.urbit.org/guides/core/hoon-school
- Hoon Standard Library: https://developers.urbit.org/reference/hoon
- NockApp Documentation: Check the nockup project docs
Common Patterns from Prover Codebase
ID Generation
=/ new-id next-id.state
:: ... use new-id ...
state(next-id +(next-id.state))
Building Entries
=/ entry ^- snark-entry
:* id
proof
inputs
vk
system
submitter
now
%pending
~
notes
==
Map Insert Pattern
=/ updated-state
state(snarks (~(put by snarks.state) new-id entry), next-id +(next-id.state))
Response with State
:_ updated-state
:~ [%http-response 201 (crip (format-submit-response new-id))]
[%log (crip "SNARK #{(scow %ud new-id)} submitted")]
==