Lesson 9: Producing Code

recordings:

Lesson 9 (US Morning) YouTube | FileBase

Lesson 9 (US Evening) YouTube | FileBase

objectives:

runes:

keypoints:

homework:

Producing Code

Logistics:

An Ætiology of Error

We distinguish different aspects of programming flaws based on how they relate to observation:

We casually refer to all of these as bugs.

An exception is a manifestation of unexpected behavior which can give rise to a failure, but some languages—notably Python—use exceptions in normal processing as well.

Failure Sources

Let’s enumerate the errors we know about at this point:

nest-fail

nest-fail may be the most common. Likely you are using an atom or a cell where the other is expected.

(add 'a' 'b') 195 (add "a" "b") -need.@ -have.[i=@tD t=""] nest-fail dojo: hoon expression failed

mint-*

mint errors arise from typechecking errors:

For instance, conversion without casting via auras fails because the atom types (auras) don't nest without explicit downcasting to @.

fish-loop

A fish-loop arises when using a recursive mold definition like list. (The relevant mnemonic is that ++fish goes fishing for the type of an expression.) Alas, this fails today:

?=((list @) ~[1 2 3 4]) [%test ~[[%.y p=2]]] fish-loop

although a promised ?# wuthax rune should match it once implemented.

generator-build-fail

A generator-build-fail most commonly results from composing code with mismatched runes (and thus the wrong children including hanging expected-but-empty slots).

mull-grow

mull-grow means it’s compiling the callsite of a wet gate (a generic gate; we’ll see these later).

bail

If you really crash things hard—crash the executable itself—then it’s a bail, which has several modes including the following:

Misusing $ buc

Another common mistake is to attempt to use the default $ buc arm in something that doesn't have it. This typically happens for one of two reasons:

Debugging Strategies

Strategies

What are some strategies for debugging?

Running without Networking

If you run the Urbit executable with -L, you cut off external networking. This is helpful if you want to mess with a copy of your actual ship without producing remote effects. That is, if other parts of Ames don’t know what you’re doing, then you can delete that copy (COPY!) of your pier and continue with the original. This is an alternative to using fakezods which is occasionally helpful in debugging userspace apps in Gall. You can also develop using a moon if you want to.

%dbug Agent Wrapper

Many Gall agents have a core wrapping them called %dbug which registers the agent for visibility of the internal state. You can start a debugging console with |start %dbug and access it at your ship’s URL followed by debug (e.g., http://localhost:8080/debug).

Profiling

I have found a reference to profiling support in the docs. ~$ sigbuc also plays a role as a profiling hit counter but I’ve not seen it used in practice as it would be stripped out of kernel code before being released.
{: .callout}

Quality Hoon

The core Urbit developers suggest grading code according to certain stylistic and functional criteria:

But don’t produce A code on the first pass! Let the code mature for a while at C or B before you refine it into final form.

Unit Testing

Testing is designed to manifest failures so that faults and errors can be identified and corrected. We can classify a testing regimen into various layers:

  1. Fences are barriers employed to block program execution if the state isn’t adequate to the intended task. Typically, these are implemented with assert or similar enforcement. In Hoon, this means ?> wutgar, ?< wutgal, and ?~ wutsig. For conditions that must succeed, the failure branch in Hoon should be !!, which crashes the program.

  2. “Unit tests are so called because they exercise the functionality of the code by interrogating individual functions and methods. Functions and methods can often be considered the atomic units of software because they are indivisible. However, what is considered to be the smallest code unit is subjective. The body of a function can be long are short, and shorter functions are arguably more unit-like than long ones.” (Huff, “Python Testing and Continuous Integration”)

    In many other languages, unit tests refer to functions, often prefixed test, that specify (and enforce) the expected behavior of a given function. Unit tests typically contain setup, assertions, and tear-down. In academic terms, they’re a grading script.

    In Hoon, the tests/ directory contains the relevant tests for the testing framework to grab and utilize. These can be invoked with the -test thread:

    -test /=landscape=/tests ~ built   /tests/lib/pull-hook-virt/hoon built   /tests/lib/versioning/hoon   test-supported: took 1047µs OK      /lib/versioning/test-supported   test-read-version: took 28317µs OK      /lib/versioning/test-read-version   test-is-root: took 28786µs OK      /lib/versioning/test-is-root   test-current-version: took 507µs OK      /lib/versioning/test-current-version   test-append-version: took 4804µs OK      /lib/versioning/test-append-version   test-mule-scry-bad-time: took 8437µs OK      /lib/pull-hook-virt/test-mule-scry-bad-time   test-mule-scry-bad-ship: took 8279µs OK      /lib/pull-hook-virt/test-mule-scry-bad-ship   test-kick-mule: took 4614µs OK      /lib/pull-hook-virt/test-kick-mule ok=%.y

    (Depending on when you built your fakezod, particular tests may or may not be present. You can download them from the Urbit repo and add them manually if you like.)

    Hoon unit tests come in two categories:

    1. ++expect-eq (equality of two values)

    2. ++expect-fail (failure/crash)

    Consider an absolute value arm ++absolute for @rs values. The unit tests for ++absolute should accomplish a few things:

  1. Integration tests check on how well your new or updated code integrates with the broader system. These can be included in continuous integration (CI) frameworks like GitHub Actions. The Arvo ecosystem isn’t large enough for developers outside the kernel itself to worry about this yet, but it will get there very soon.

Producing Error Messages

Formal error messages in Urbit are built of tanks. “A tang is a list of tanks, and a tank is a structure for printing data. There are three types of tank: leaf, palm, and rose. A leaf is for printing a single noun, a rose is for printing rows of data, and a palm is for printing backstep-indented lists.” (You saw something of these in Lesson 7.)

One way to include an error message in your code is the ~_ sigcab rune, described as a “user-formatted tracing printf”, or the ~| sigbar rune, a “tracing printf”. What this means is that these print to the stack trace if something fails, so you can use either rune to contribute to the error description:

|= [a=@ud] ~_ leaf+"This code failed" !!

When you compose your own library functions, consider including error messages for likely failure points.

Working Across Desks

Desks organize collections of files on Urbit: data files, libraries, marks, agents, etc. So far everything we have done has taken place on the %base desk.

You can see the desks available on your system with the +vats generator.

+vats

Any of these can be |mounted to Earth and |commited on demand.

Of course, since %base is our default working desk, we haven't had to create or arrange any other desks. If we were to do so, we could mark a desk as public for distribution and share it with the world. That's our objective now.

Invoking Code on a Desk

First off, let's take a look at how to run a generator on another desk. A standard install has a few desks: %bitcoin, %garden, %landscape. You can invoke a particular desk's version of a generator by prefixing the desk name with ! zap:

+landscape!tally tallied your activity score! find the results below. to show non-anonymized resource identifiers, +tally | counted from groups and channels that you are hosting. groups are listed with their member count. channels are listed with activity from the past week:  - amount of top-level content  - amount of unique authors the date is ~2022.5.5..19.22.40..cf63 you are in 0 group(s): you are hosting 0 group(s):

Distributing Code using a Desk

The following is best done using a networked ship, likely a new moon. The guide works in the opposite order from this tutorial, which gives you a chance to think about how to accomplish this both ways.

The basic concept of software distribution for Urbit is that a ship has a desk with self-contained agent code and a %bill mark which indicates any automatically running agents (upon installation).

Broadly, speaking, desks look the same, except for some modest additions for agent registration. The directories still obtain as follows:

These new files contain critical information to instrument the distributed software:

Ideally, a desk would present a clean slate, but since any desk must be branched off of an existing desk, there is a history of files for the time being. (Someday we may introduce a new clean desk for developers to add to.)

Since we don't have a Gall agent associated with HSL, we will leave off the desk.bill and desk.docket-0 files.

Create a new desk and mount it:

|merge %sandbox our %base, =gem %init

+ls /=sandbox= app/ desk/bill gen/ lib/ mar/ sur/ sys/ ted/ |mount %sandbox

Now you can add or remove contents from the desk on Earth and |commit them to the new desk on Mars. In particular, while we can add generators, you should make sure to clear the desk.bill file to ~ so that you don't have multiple copies of %base agents running.

The docket file is read by the %docket agent when a desk is |installed. The %docket agent will fetch the glob if applicable and create the tile as specified on the homescreen. If the desk is published with :treaty|publish, the information specified in the docket file will also be displayed for others who are browsing apps to install on your ship.

Once you have arranged the contents as you will, you can |install the desk. You have two options:

  1. Mark it as |public. This makes the desk mergeable by others.

    |install our %sandbox |public %sandbox

  2. Mark it for distribution using Grid. This makes others able to install it via the Grid interface on their livenet ship. (This also marks the desk as |public.)

    |install our %sandbox :treaty|publish %sandbox

(Depending on the pill you used to start your ship, this could be the tetchiest step. Copy in the /mar files from an up-to-date ship if you need to.)

Now anyone on the network can |install code from your ship while it is running, e.g.

|install ~sampel-sampel-lagrev-nocfep %sandbox

Not every desk has to have a docket file and a set of agents to run, but many will. That will be the objective of App School Live and the Gall Guide on urbit.org.

When you push production code, you should include unit tests and documentation.