Urbit Docs
  • What is Urbit?
  • Get on Urbit
  • Build on Urbit
    • Contents
    • Environment Setup
    • Hoon School
      • 1. Hoon Syntax
      • 2. Azimuth (Urbit ID)
      • 3. Gates (Functions)
      • 4. Molds (Types)
      • 5. Cores
      • 6. Trees and Addressing
      • 7. Libraries
      • 8. Testing Code
      • 9. Text Processing I
      • 10. Cores and Doors
      • 11. Data Structures
      • 12. Type Checking
      • 13. Conditional Logic
      • 14. Subject-Oriented Programming
      • 15. Text Processing II
      • 16. Functional Programming
      • 17. Text Processing III
      • 18. Generic and Variant Cores
      • 19. Mathematics
    • App School I
      • 1. Arvo
      • 2. The Agent Core
      • 3. Imports and Aliases
      • 4. Lifecycle
      • 5. Cards
      • 6. Pokes
      • 7. Structures and Marks
      • 8. Subscriptions
      • 9. Vanes
      • 10. Scries
      • 11. Failure
      • 12. Next Steps
      • Appendix: Types
    • App School II (Full-Stack)
      • 1. Types
      • 2. Agent
      • 3. JSON
      • 4. Marks
      • 5. Eyre
      • 6. React app setup
      • 7. React app logic
      • 8. Desk and glob
      • 9. Summary
    • Core Academy
      • 1. Evaluating Nock
      • 2. Building Hoon
      • 3. The Core Stack
      • 4. Arvo I: The Main Sequence
      • 5. Arvo II: The Boot Sequence
      • 6. Vere I: u3 and the Serf
      • 7. Vere II: The Loom
      • 8. Vanes I: Behn, Dill, Kahn, Lick
      • 9. Vanes II: Ames
      • 10. Vanes III: Eyre, Iris
      • 11. Vanes IV: Clay
      • 12. Vanes V: Gall and Userspace
      • 13. Vanes VI: Khan, Lick
      • 14. Vanes VII: Jael, Azimuth
    • Runtime
      • U3
      • Conn.c Guide
      • How to Write a Jet
      • API Overview by Prefix
      • C in Urbit
      • Cryptography
      • Land of Nouns
    • Tools
      • Useful Links
      • JS Libraries
        • HTTP API
      • Docs App
        • File Format
        • Index File
        • Suggested Structure
    • Userspace
      • Command-Line App Tutorial
      • Remote Scry
      • Unit Tests
      • Software Distribution
        • Software Distribution Guide
        • Docket File
        • Glob
      • Examples
        • Building a CLI App
        • Debugging Wrapper
        • Host a Website
        • Serving a JS Game
        • Ship Monitoring
        • Styled Text
  • Urbit ID
    • What is Urbit ID?
    • Azimuth Data Flow
    • Life and Rift
    • Urbit HD Wallet
    • Advanced Azimuth Tools
    • Custom Roller Tutorial
    • Azimuth.eth Reference
    • Ecliptic.eth Reference
    • Layer 2
      • L2 Actions
      • L2 Rollers
      • L2 Roller HTTP RPC-API
      • L2 Transaction Format
  • Urbit OS
    • What is Urbit OS?
    • Base
      • Hood
      • Threads
        • Basics Tutorial
          • Bind
          • Fundamentals
          • Input
          • Output
          • Summary
        • HTTP API Guide
        • Spider API Reference
        • Strandio Reference
        • Examples
          • Child Thread
          • Fetch JSON
          • Gall
            • Poke Thread
            • Start Thread
            • Stop Thread
            • Take Facts
            • Take Result
          • Main-loop
          • Poke Agent
          • Scry
          • Take Fact
    • Kernel
      • Arvo
        • Cryptography
        • Move Trace
        • Scries
        • Subscriptions
      • Ames
        • Ames API Reference
        • Ames Cryptography
        • Ames Data Types
        • Ames Scry Reference
      • Behn
        • Behn API Reference
        • Behn Examples
        • Behn Scry Reference
      • Clay
        • Clay API Reference
        • Clay Architecture
        • Clay Data Types
        • Clay Examples
        • Clay Scry Reference
        • Filesystem Hierarchy
        • Marks
          • Mark Examples
          • Using Marks
          • Writing Marks
        • Using Clay
      • Dill
        • Dill API Reference
        • Dill Data Types
        • Dill Scry Reference
      • Eyre
        • EAuth
        • Eyre Data Types
        • Eyre External API
        • Eyre Internal API
        • Eyre Scry Reference
        • Low-Level Eyre Guide
        • Noun channels
      • Gall
        • Gall API Reference
        • Gall Data Types
        • Gall Scry Reference
      • Iris
        • Iris API Reference
        • Iris Data Types
        • Iris Example
      • Jael
        • Jael API Reference
        • Jael Data Types
        • Jael Examples
        • Jael Scry Reference
      • Khan
        • Khan API Reference
        • Khan Data Types
        • Khan Example
      • Lick
        • Lick API Reference
        • Lick Guide
        • Lick Examples
        • Lick Scry Reference
  • Hoon
    • Why Hoon?
    • Advanced Types
    • Arvo
    • Auras
    • Basic Types
    • Cheat Sheet
    • Cryptography
    • Examples
      • ABC Blocks
      • Competitive Programming
      • Emirp
      • Gleichniszahlenreihe
      • Islands
      • Luhn Number
      • Minimum Path Sum
      • Phone Letters
      • Restore IP
      • Rhonda Numbers
      • Roman Numerals
      • Solitaire Cipher
      • Water Towers
    • Generators
    • Hoon Errors
    • Hoon Style Guide
    • Implementing an Aura
    • Irregular forms
    • JSON
    • Limbs and wings
      • Limbs
      • Wings
    • Mips (Maps of Maps)
    • Parsing Text
    • Runes
      • | bar · Cores
      • $ buc · Structures
      • % cen · Calls
      • : col · Cells
      • . dot · Nock
      • / fas · Imports
      • ^ ket · Casts
      • + lus · Arms
      • ; mic · Make
      • ~ sig · Hints
      • = tis · Subject
      • ? wut · Conditionals
      • ! zap · Wild
      • Constants (Atoms and Strings)
      • --, == · Terminators
    • Sail (HTML)
    • Serialization
    • Sets
    • Standard Library
      • 1a: Basic Arithmetic
      • 1b: Tree Addressing
      • 1c: Molds and Mold-Builders
      • 2a: Unit Logic
      • 2b: List Logic
      • 2c: Bit Arithmetic
      • 2d: Bit Logic
      • 2e: Insecure Hashing
      • 2f: Noun Ordering
      • 2g: Unsigned Powers
      • 2h: Set Logic
      • 2i: Map Logic
      • 2j: Jar and Jug Logic
      • 2k: Queue Logic
      • 2l: Container from Container
      • 2m: Container from Noun
      • 2n: Functional Hacks
      • 2o: Normalizing Containers
      • 2p: Serialization
      • 2q: Molds and Mold-Builders
      • 3a: Modular and Signed Ints
      • 3b: Floating Point
      • 3c: Urbit Time
      • 3d: SHA Hash Family
      • 3e: AES encryption (Removed)
      • 3f: Scrambling
      • 3g: Molds and Mold-Builders
      • 4a: Exotic Bases
      • 4b: Text Processing
      • 4c: Tank Printer
      • 4d: Parsing (Tracing)
      • 4e: Parsing (Combinators)
      • 4f: Parsing (Rule-Builders)
      • 4g: Parsing (Outside Caller)
      • 4h: Parsing (ASCII Glyphs)
      • 4i: Parsing (Useful Idioms)
      • 4j: Parsing (Bases and Base Digits)
      • 4k: Atom Printing
      • 4l: Atom Parsing
      • 4m: Formatting Functions
      • 4n: Virtualization
      • 4o: Molds
      • 5a: Compiler Utilities
      • 5b: Macro Expansion
      • 5c: Compiler Backend & Prettyprinter
      • 5d: Parser
      • 5e: Molds and mold builders
      • 5f: Profiling support
    • Strings
    • The Engine Pattern
    • Udon (Markdown-esque)
    • Vases
    • Zuse
      • 2d(1-5): To JSON, Wains
      • 2d(6): From JSON
      • 2d(7): From JSON (unit)
      • 2e(2-3): Print & Parse JSON
      • 2m: Ordered Maps
  • Nock
    • What is Nock?
    • Decrement
    • Definition
    • Fast Hints and Jets
    • Implementations
    • Specification
  • User Manual
    • Contents
    • Running Urbit
      • Cloud Hosting
      • Home Servers
      • Runtime Reference
      • Self-hosting S3 Storage with MinIO
    • Urbit ID
      • Bridge Troubleshooting
      • Creating an Invite Pool
      • Get an Urbit ID
      • Guide to Factory Resets
      • HD Wallet (Master Ticket)
      • Layer 2 for planets
      • Layer 2 for stars
      • Proxies
      • Using Bridge
    • Urbit OS
      • Basics
      • Configuring S3 Storage
      • Dojo Tools
      • Filesystem
      • Shell
      • Ship Troubleshooting
      • Star and Galaxy Operations
      • Updates
Powered by GitBook

GitHub

  • Urbit ID
  • Urbit OS
  • Runtime

Resources

  • YouTube
  • Whitepaper
  • Awesome Urbit

Contact

  • X
  • Email
  • Gather
On this page
  • Example: Sexagesimal Degrees
  • Preliminaries
  • Design
  • Base Logic
  • Pretty-Printing
  • Parsing
  • Exercises
Edit on GitHub
  1. Hoon

Implementing an Aura

An aura is a metadata tag for Hoon to identify and manipulate atoms correctly. Auras nest inside of each other logically: @uvJ nests inside of @uv, which in turn nests in @u then @. Auras allow programmers to encode intent into their handling of values.

To implement an aura in Hoon, we follow these steps:

  1. Design the aura. A lot of existing letter pairs are spoken for, and new auras should nest logically. Currently unoccupied letters include: abeghjkmovwxyz. If you can make a case for why your new aura logically nests under an existing aura, then you implement such a nesting; you'll just need to make the case for it in your pull request submission.

  2. Implement the base logic in /sys/hoon. This, by and large, means providing a core or door which can correctly carry out arithmetic, conversion, processing, and so forth on your atom.

  3. Implement a pretty-printer in |co. This should match the atom syntax from the next step.

  4. Implement parser in |so. Compose a parsing rule which is distinct from all others and add it in the appropriate sections here. (Finding a unique syntax that follows Hoon's rules, like URL-safe characters only and not parseable as Hoon code, can be rather challenging now.) The aura parser prefixes elements with a type term; properly the pair is called a dime. This will also allow you to type the literal atom in the Dojo.

We prefer the parsed form and the prettyprinted form to coincide so we can copy and paste a result directly back in as valid Hoon. (This is generally true for atoms but not for other molds; consider set for instance.)

Example: Sexagesimal Degrees

Classically, angular measurements using degrees subdivided each degree into 60 minutes and each minute into 60 seconds. Although less common in an age rife with floating-point values, proficiency with sexagesimal notation lends distinction and gravitas.

5°6′7′′5°6'7''5°6′7′′

Preliminaries

You should fork the Urbit repo and create a working branch for this project. While per the guide you will not be PRing this code back against the main repo, this is the standard working environment setup.

Design

In this example, we will produce a degree–minute–second (DMS) aura. We will call it @udms. It will have the visual form of three integers prefixed with . dots, e.g. .359.59.59 for 359°59'59''. This distinguishes it from @rs, which has two similarly-separated values, e.g. .1.2; and from @if, which has four, e.g. .1.2.3.4. For atoms, the literal input form and literal output form should be the same.

It will have the bit-logical form of an integer of arbitrary size, with each whole-number increment representing a second. This would permit overflow, i.e. values greater than 360° such as 720°; however, we will supply a ++norm arm to enforce modular arithmetic at 360°.

Base Logic

We need to be able to perform arithmetic and type conversion with @udms values. Some value representations have an “unpacked“ tuple form, like dates and floating-point values. This makes it easier to shunt the values between auxiliary functions. We'll define one as well here, +$sexa (for sexagesimal, base-60).

At this point, we implement modular arithmetic and wrap the values properly in ++op. For instance, wrapping around at 360°=0° should work properly, similar to midnight. Subtraction is liable to underflow, so we need a special handler for it in ++dg; since we have one, we may as well handle ++add the same way for consistency.

359°+2°=1°359° + 2° = 1°359°+2°=1°

\

59′+1′=1°59' + 1' = 1°59′+1′=1°

\

59′′+1′′=1′59'' + 1'' = 1'59′′+1′′=1′

\

1°59′59′′+1′′=2°1°59'59'' + 1'' = 2°1°59′59′′+1′′=2°

\

3°−1°=2°3° - 1° = 2°3°−1°=2°

\

1°−3°=358°1° - 3° = 358°1°−3°=358°

\

0°−1′′=359°59′59′′0° - 1'' = 359°59'59''0°−1′′=359°59′59′′

Let's write some unit tests first.

/tests/sys/dms

/+  *test
|%
++  test-add-zero
  ;:  weld
  %+  expect-eq
    !>  (wrap:dg 180 0 0)
    !>  (add:dg (wrap:dg 180 0 0) (wrap:dg 0 0 0))
  ==
++  test-add-dms
  ;:  weld
  %+  expect-eq
    !>  (wrap:dg 180 0 0)
    !>  (add:dg (wrap:dg 179 0 0) (wrap:dg 1 0 0))
  %+  expect-eq
    !>  (wrap:dg 180 0 0)
    !>  (add:dg (wrap:dg 90 1 0) (wrap:dg 89 59 0))
  %+  expect-eq
    !>  (wrap:dg 180 0 0)
    !>  (add:dg (wrap:dg 1 1 1) (wrap:dg 178 58 59))
  %+  expect-eq
    !>  (wrap:dg 180 0 0)
    !>  (add:dg (wrap:dg 179 0 59) (wrap:dg 0 0 1))
  ==
++  test-sub-dms
  ;:  weld
  %+  expect-eq
    !>  (wrap:dg 90 0 0)
    !>  (sub:dg (wrap:dg 180 0 0) (wrap:dg 90 0 0))
  %+  expect-eq
    !>  (wrap:dg 90 0 0)
    !>  (sub:dg (wrap:dg 0 0 0) (wrap:dg 270 0 0))
  %+  expect-eq
    !>  (wrap:dg 90 0 0)
    !>  (sub:dg (wrap:dg 90 0 0) (wrap:dg 360 0 0))
  %+  expect-eq
    !>  (wrap:dg 89 0 0)
    !>  (sub:dg (wrap:dg 90 0 0) (wrap:dg 1 0 0))
  %+  expect-eq
    !>  (wrap:dg 89 59 0)
    !>  (sub:dg (wrap:dg 90 0 0) (wrap:dg 0 1 0))
  %+  expect-eq
    !>  (wrap:dg 89 59 59)
    !>  (sub:dg (wrap:dg 90 0 0) (wrap:dg 0 0 1))
  %+  expect-eq
    !>  (wrap:dg 359 59 59)
    !>  (sub:dg (wrap:dg 0 0 0) (wrap:dg 0 0 1))
  ==
++  test-unwrap-dms
  ;:  weld
  %+  expect-eq
    !>  .90.0.0
    !>  (wrap:dg 90 0 0)
  %+  expect-eq
    !>  .0.45.0
    !>  (wrap:dg 0 45 0)
  %+  expect-eq
    !>  .0.0.45
    !>  (wrap:dg 0 0 45)
  %+  expect-eq
    !>  .1.2.3
    !>  (wrap:dg 1 2 3)
  %+  expect-eq
    !>  .360.0.0
    !>  (wrap:dg 360 0 0)
  ==
++  test-norm-dms
  ;:  weld
  %+  expect-eq
    !>  (wrap:dg 1 30 0)
    !>  (norm:dg (wrap:dg 0 90 0))
  %+  expect-eq
    !>  (wrap:dg 1 0 0)
    !>  (norm:dg (wrap:dg 0 60 0))
  %+  expect-eq
    !>  (wrap:dg 0 1 30)
    !>  (norm:dg (wrap:dg 0 0 90))
  %+  expect-eq
    !>  (wrap:dg 0 0 0)
    !>  (norm:dg (wrap:dg 360 0 0))
  %+  expect-eq
    !>  (wrap:dg 0 0 0)
    !>  (norm:dg (wrap:dg 359 60 0))
  %+  expect-eq
    !>  (wrap:dg 0 0 0)
    !>  (norm:dg (wrap:dg 359 59 60))
  %+  expect-eq
    !>  .1.0.0
    !>  .0.60.0
  ==
++  test-parse-dms
  ;:  weld
  %+  expect-eq
    !>  [p=[p=1 q=6] q=[~ u=[p=3.723 q=[p=[p=1 q=6] q=""]]]]
    !>  (dems:so [[1 1] "1.2.3"])
  %+  expect-eq
    !>  3.723
    !>  (scan "1.2.3" dems:so)
  %-  expect-fail
    |.  (scan ".5.6" ;~(pfix dot ;~((glue dot) dem:ag dem:ag dem:ag)))
  %-  expect-fail
    |.  (scan ".5.6.7.8" ;~(pfix dot ;~((glue dot) dem:ag dem:ag dem:ag)))
  ==
--

The Hoon logic will be located in a ++dg arm. This needs to be sufficiently high in the stack that our prettyprinter and parser logic know about them, so let's put ++dg in the fourth core. Search for layer-5 and paste ++dg in a few lines above that after +$hump. (Strictly speaking, we should make sure that this works in a userspace /lib library first but here we'll just insert it into /sys/hoon and rely on our unit tests.)

/sys/hoon

++  dg
  |%
  +$  sexa  $:(d=@ud m=@ud s=@ud)
  :: Convert to $sexa expanded form.
  ++  unwrap
    |=  p=@udms
    ^-  sexa
    =/  d  (div p 3.600)
    =/  s  (sub p (mul d 3.600))
    =/  m  (div s 60)
    =.  s  (sub s (mul m 60))
    [d m s]
  :: Convert to atomic @udms form.
  ++  wrap
    |=  =sexa
    ^-  @udms
    =,  sexa
    :(add (mul d 3.600) (mul m 60) s)
  :: Handle arithmetic.
  ++  add  |=([p=@udms q=@udms] ^-(@udms (^add `@`p `@`q)))
  ++  sub  |=([p=@udms q=@udms] ^-(@udms (~(dif fo 1.296.000) `@`p `@`q)))
  :: Convert to @rs.
  ++  to-rs  |=(p=@udms ^-(@rs (div:rs (sun:rs p) .3600)))
  :: Convert to @rd.
  ++  to-rd  |=(p=@udms ^-(@rd (div:rd (sun:rd p) .~3600)))
  :: Roll over values out of range of 360°.
  ++  norm
    |=  p=@udms
    ^-  @udms
    (~(fra fo 1.296.000) `@`p 1.296.000)
  --

Pretty-Printing

The atom literal should be constructed in ++rend, which has a cascade of switch statements over the atom aura. Let's adopt the output syntax to be the same as the input syntax, .ddd.mm.ss.

++  co
  ~%  %co  ..co  ~
  |_
    ...
    ++  rend
      =+  [yed=(end 3 p.p.lot) hay=(cut 3 [1 1] p.p.lot)]
      |-  ^-  tape
      ?+    yed  (z-co q.p.lot)
        ...
            %u
          ?:  ?=(%c hay)
            %+  welp  ['0' 'c' (reap (pad:fa q.p.lot) '1')]
            (c-co (enc:fa q.p.lot))
          ::
          =;  gam=(pair tape tape)
            (weld p.gam ?:(=(0 q.p.lot) `tape`['0' ~] q.gam))
          ?+  hay  [~ ((ox-co [10 3] |=(a=@ ~(d ne a))) q.p.lot)]
          ...
              %d
            =/  ham  (cut 3 [2 1] p.p.lot)
            ?+    ham  [~ ((ox-co [10 3] |=(a=@ ~(d ne a))) q.p.lot)]
                %m
              =/  has  (cut 3 [3 1] p.p.lot)
              ?+    has  [~ ((ox-co [10 3] |=(a=@ ~(d ne a))) q.p.lot)]
                  %s
                =/  =sexa:dg  (unwrap:dg q.p.lot)
                :-  ['.' ~]
                `tape`:(weld (a-co d.sexa) "." (a-co m.sexa) "." (a-co s.sexa))
              ==
            ==
          ==

Every type has its own special conventions, so you need to adapt to follow whatever those may be.

Parsing

A parsing rule which correctly handles the aura is:

;~(pfix dot ;~((glue dot) dem:ag dem:ag dem:ag))

as demonstrated by these tests. (See also ++dem:ag.)

> (;~(pfix dot ;~((glue dot) dem:ag dem:ag dem:ag)) [[1 1] ".1.2.3"])
[p=[p=1 q=7] q=[~ u=[p=[1 2 3] q=[p=[p=1 q=7] q=""]]]]

> (scan ".5.6.7" ;~(pfix dot ;~((glue dot) dem:ag dem:ag dem:ag)))
[5 6 7]

> (scan ".5.6" ;~(pfix dot ;~((glue dot) dem:ag dem:ag dem:ag)))
{1 5}
syntax error
dojo: hoon expression failed

> (scan ".5.6.7.8" ;~(pfix dot ;~((glue dot) dem:ag dem:ag dem:ag)))
{1 9}
syntax error
dojo: hoon expression failed

Ultimately we will pack these together into the atom form @udms.

> (scan "1.2.3" (cook |=([d=@ m=@ s=@] :(add (mul d 3.600) (mul m 60) s)) ;~((glue dot) dem:ag dem:ag dem:ag)))
3.723

Note that this form overflows still, but can be corrected using ++norm:dg.

Our parser logic needs to be cited in ++zust:so because that arm parses atoms prefixed with . dot. This means that the pfix dot gets eaten, and our actual parsing rule only needs to handle the rest of the symbol. Add a ++dems arm after ++zust and register it in the pose in ++zust as well. Since parsers in a pose resolve in order, it should come after IPv4 @if addresses (.1.1.1.1) and before @rs values (.1.1).

++  so
  ~%  %so  +  ~
  |%
  ...
  ++  zust
    ~+
    ;~  pose
      (stag %is bip:ag)
      (stag %if lip:ag)
      (stag %udms dems)
      royl
      ...
    ==
  ++  dems
    |=  b=nail
    %.  b
    %+  cook
      |=([d=@ m=@ s=@] :(add (mul d 3.600) (mul m 60) s))
    ;~((glue dot) dem:ag dem:ag dem:ag)
  --

You also need to modify +$iota to include [%udms @udms] to satisfy the type system for typed paths.

At this point, a compiled /sys/hoon should detect the typed atom syntax correctly. This aura should pass all tests and be usable in conventional Hoon code.

> .1.2.3
.1.2.3

> `@ud`.1.2.3
3.723

Exercises

What else should be implemented?

  • Radian-based angles could be supported (let's say as @ur).

  • Arguably, there should be an absolute angle (analogous to @da) representing a direction in 2D space, and a relative angle (analogous to @dr) representing a turn in 2D space.

  • You'd also want to decide how to deal with negative arcs, if supported; a zigzag scheme similar to @s would be most apt (@sdms).

  • A spherical trigonometry project could build on these mathematics for an astronomical or avionic calculator. (@uds for steradians?)

  • *@udms displays incorrectly. Why, and how would you fix it?

PreviousHoon Style GuideNextIrregular forms

Last updated 1 day ago