26 February 2023

urbit userspace style guide

Hoon is a fine language, it just needs consistency.

AJ LaMarc
AJ LaMarc @ajlamarc

Priors

Currently there is a lack of convention around naming, Gall agent structure, how to organize files, etc. Since Hoon is also highly irregular, this is a big roadblock to understanding other developers’ code. This is my initial take at consistent Hoon style, and will be updated over time.

Runes

Avoid means do not use.

If a rune is not mentioned, the expectation is that it will be used very rarely.

= tis · Subject

  • Use tisfas (=/) for creating variables.
    • Avoid tislus (=+) and tishep (=-).
  • Use tisdot (=.) to modify existing variables.
    • Avoid tiswut (=?) and tiscol (=:).
  • tisket (=^), tistar (=*), and tismic (=,) are useful in specific areas.

| bar · Cores

  • Use bartis (|=), barcab (|_), and barcen (|%).

$ buc · Structures

  • Use buccol ($:) in structure mode in either wide or tall form based on line width.
  • Prefer buccen ($%) in tall form, bucwut ($?) in wide form. Example:
+$  kv-update
  $%  [%set =key =value]
      [%get value=?(~ json-value)]
      [%allow write=?(%yes %no) admin=?(%yes %no)]
  ==

% cen · Calls

  • Prefer censig (%~) in irregular form for calling doors i.e. (~(put by map) 'key' 'value')
  • cenhep (%-) and centis (%=) can be used in wide or tall form based on line width.
  • cendot (%.) and cenlus (%+) should be used rarely.
  • Avoid cencab (%_), cencol (%:), cenket (%^), and centar (%*).

% col · Cells

  • Use colhep (:-), colcab (:_), coltar (:*), and colsig (:~).
  • Avoid collus (:+) and colket (:^); use coltar (:*) instead.

. dot · Nock

  • Use dotket (.^) to scry.
  • Use dotlus(.+) and dottis (.=) in irregular forms +(a) and =(a b) respectively.

/ fas · Imports

  • Use fashep (/-) and faslus (/+) for sur and lib imports respectively.

^ ket · Casts

  • Use kethep (^-) casts liberally, in wide and irregular form like `@ud`~zod.
  • Use ketlus (^+) in tall form.
  • Use kettar (^*) in irregular form *a.

~ sig · Hints

  • Use sigpam (~&) and sigbar (~|).

? wut · Conditionals

  • These runes are generally all useful.
  • Avoid wutgar (?>) and wutgal (?<) (assertions). Instead, provide an error message alongside the crash:
?.  =(our.bol src.bol)  ~|(%no-foreign-init/act !!)

! zap · Wild

  • Use zapgar (!>) and zapgal (!<) for vases.
  • Use zapzap (!!) to crash, including an error message.

app (agent)

  • Start with the following boilerplate.
  • Use the agentio library wherever possible.
  • Use the nested core pattern. This leaves all the boilerplate at the top of the file, and makes it easy to see which agent arms are implemented and which just have default behavior.
  • The helper core cor will create its own nested cores as necessary. Nested cores will have a two-letter name that prefixes all its arms. For a full example, see tome-db.
  • Use pol=(pole knot) over pax=path. This lets you provide names to path values for clarity.
:: good
?+    pol  ~|(bad-watch-path/pol !!)
    [%kv ship=@ space=@ app=@ bucket=@ rest=*]
  =/  ship  `@p`(slav %p ship.pol)
  ...
==

:: bad
?+    pax  ~|(bad-watch-path/pax !!)
    [%kv @ @ @ @ *]
  =/  ship  `@p`(slav %p i.t.t.t.pax)
  ...
==

sur (structure)

  • Each agent should have one sur file, named the same as its corresponding agent. It should not be in a subfolder.
  • Alternatively, if multiple agents need only a subset of the types, provide them in separate files in a subfolder named after the agent.
sur/
├─ spaces.hoon

or

sur/
├─ spaces/
   ├─ path.hoon
   ├─ store.hoon
  • Actions and update types should be named as such inside the sur file.
+$  tome-action
  ...

+$  tome-update
  ...

mar (mark)

  • Each agent should have a folder inside mar for all of its marks. There should be two mark files: action and update. If nested cores are used, their marks will be in separate subfolders.
mar/
├─ tome/
   ├─ action.hoon
   ├─ update.hoon

or

mar/
├─ tome/
   ├─ tome/
      ├─ action.hoon
      ├─ update.hoon
   ├─ kv/
      ├─ action.hoon
      ├─ update.hoon
   ├─ feed/
      ├─ action.hoon
      ├─ update.hoon
  • Mark files will defer encoding / decoding JSON to a corresponding lib file. The order of their arms will always be grow, grab, then grad. They should not vary much from the following:
::  tome/action.hoon
/-  *tome
/+  *tome-json
|_  act=tome-action
++  grow
  |%
  ++  noun  act
  --
::
++  grab
  |%
  ++  noun  tome-action
  ++  json  tome-action:dejs
  --
++  grad  %noun
::
--
::  tome/update.hoon
/-  *tome
/+  *tome-json
|_  upd=tome-update
++  grow
  |%
  ++  noun  upd
  ++  json  (tome-update:enjs upd)
  --
::
++  grab
  |%
  ++  noun  tome-update
  --
::
++  grad  %noun
::
--

lib (library)

  • There will be a folder inside lib with the same name as the corresponding agent. Inside that folder will be json.hoon.
lib/
├─ tome/
   ├─ json.hoon
  • json.hoon is a core with two arms dejs and enjs. dejs will contain xx-action arms, and enjs will contain xx-update arms. Example:
/-  *tome
|%
++  dejs  =,  dejs:format
  |%
  ++  tome-action
    |=  jon=json
    ^-  ^tome-action
    %.  jon
    %-  of
    :~  init-tome/(ot ~[ship/so space/so app/so])
        init-kv/(ot ~[ship/so space/so app/so bucket/so])
        init-feed/(ot ~[ship/so space/so app/so bucket/so log/bo])
    ==
  ::
  --
::
++  enjs  =,  enjs:format
  |%
  ++  kv-update
    |=  upd=^kv-update
    ^-  json
    ?-  -.upd
      %set     (frond key.upd s+value.upd)
      %remove  (frond key.upd ~)
      %get     value.upd
      %all     o+data.upd
    ==
  ::
  --
::
--

Refer to the tome-db code for a full reference.

Inspired by Google’s Python style guide.

Categories

urbit