Hock and Loon

Hock and Loon

Hock and Loon look different from Nock and Hoon, but they aren't really. Everything discussed here is already available to Nock. I only change Nock into Hock to emphasize the differences in what sorts of kernel scheme are available to us in a solid-state interpreter. Loon is just there for ergonomic reasons.

Hock

The differences between Hock and Nock are superficial. It's trivial in most cases to translate between the two. But Hock explicates two subtleties of Nock: you can use opcodes outside of the official Nock specification, which Gall already does, and interpreters are allowed to expand their productions in whatever order they choose. These features allow Hock to manage a more conventional user environment, with certain advantages of preemptive operating systems but without sacrificing functional determinism.

Hock 5K:

^[_ _]          0
^_              1
+[_ _]          _
+n              S(n)
=[x x]          0
=[_ _]          1
/[1 x]          x
/[2 [x _]]      x
/[3 [_ x]]      x
/[(n + n) x]    /[2 /[n x]]
/[S(n + n) x]   /[3 /[n x]]

*[[0 x] _]      x
*[[1 n] x]      /[n x]
*[[2 f] x]      **[f x]
*[[3 f] x]      ^*[f x]
*[[4 f] x]      +*[f x]
*[[5 f] x]      =*[f x]

... extensions apply here ...

*[[f g] x]      [*[f x] *[g x]]
*x              _

0 and 1 are rearranged, 2 has been restored to its former glory, ? has been renamed to ^, # and the macro opcodes are gone (but not really), and in Hock we place the formula on the left. Hock has exactly the same capabilities as Nock, but interpreting one as the other will almost certainly crash.

Extended opcodes

Extended opcodes are either direct or indirect. Direct opcodes are always equivalent to some extensionless formula. Indirect opcodes are comparable to hardware interrupts and can never be simulated with direct opcodes.

Hock construes 6, 7, 8, 9, 10, and 11 as direct opcode extensions. Hock 12 is an example of an indirect opcode extension: the virtualizer prescribes sematics for 12 and fulfils it with information not available to the Hock expression being expanded.

Note that while only indirect opcodes can draw information from outside the subject, opcodes both direct and indirect can cause the runtime to act in the real world, such as by sending a packet. Note also that the runtime may be the same thing as the virtualizer in a non-Arvo stack.

Currently there is no prodecure to assign opcode extensions. Developers assign opcodes ad hoc and use them in their internal systems.

Parallelism

Nock is already parallel, as it doesn't specify the order of evaluation. As a purely functional combinator language, it suffers none of the usual problems of parallelism. These problems return when indirect opcodes are involved. Arvo, which already uses indirect opcodes, solves this problem by being explicitly sequential (and statically immutable).

But it's the things that aren't statically immutable that are so interesting. We want to see where the rubber hits the road. What's my CPU usage like? Is there an ISP outage or aren't I on the LAN? What's in the microphone buffer? We could talk to a sidecar through %lick… or we could just ask the runtime with an indirect opcode and get an immediate response.

How do we ensure proper ordering of opcodes that cross the kernel boundary? Even a simple formula can be expanded in an astronomical number of ways. The naïve Hock formula [7 [send f] 8 [recv g] h] may expand *[[recv g] x] before *[[send f] x], and it may partly expand *[h x] before either of those. Properly parallel Hock uses the sequencing opcode *[[13 f g] x] (indirect) to formally guarantee that *[f x] will be expanded before [*g x]. A properly parallel virtualizer handles concurrent subformulas' indirect opcodes sequentially and records their ordering and products in the event log.

One can imagine using indirect opcodes to control parallelism and introspect Hock expressions while they're being expanded.

Opcode creep?

One of Nock's strengths is the dearth of opcodes. Hock adds some and promises more. Do we really want to burden implementers with ever more opcodes? No, we don't. Indirect opcodes are strictly meant for internal use by middleware adapters such as an X11 GUI, never for computation or for communication between applications (except for a small number of standard opcode extensions).

Loon

Loon is a simplified version of Hoon. Aside from its terse syntax and the use of interfaces rather than types, its main purpose is to exploit opcode extensions in Hock to create a concurrent desktop application environment for Urbit. That being said, the Loon compiler can understand and produce Hoon cores, and Loon is a viable language for programming on Arvo.

Like Hoon, Loon is subject-oriented and uses Polish notation. Unlike Hoon, Loon is whitespace-insensitive and has relatively few special forms. Loon also abolishes the type system in favor of interfaces.

Here is a sample Loon program that reads and evaluates Loon source.

?!=[100 loon-version] _   ; Crash unless version is correct
#import ~/lib/loon/loon   ; Import the Loon compiler
{@path pax}               ; Produce a gate with path sample
.src #scry ['tape' pax]   ; Scry `pax` and pin it as `src`
.res (loon.eval src)      ; Call `loon.eval`
?=['fail' <res] res       ; If `res` is tagged with 'fail',
  #slog res ~             ;   report the error
res                       ; Otherwise forward the result

#import is a compiler directive. #scry becomes opcode 12. #slog is a debug hint similar to ~& in Hoon.

Loon syntax

Operator Description
#name expr expr Extended opcode
@name expr Apply interface
expr:name Convert to interface
[expr] Produce a noun
_ Crash
?expr expr expr Branch
&(expr expr) Logical AND
|(expr expr) Logical OR
!expr Logical NOT
%expr Set recursion point
$ Jump to recursion point
{name} | {} Produce gate or trap
(expr) Slam gate/kick trap
/expr Fetch in noun (Hock 1)
*expr Invoke (Hock 2)
^expr Cell test (Hock 3)
+expr Increment (Hock 4)
=expr Equals (Hock 5)
<expr Left child
>expr Right child
.name expr expr Create leg
..name expr expr Mutate leg
. Produce subject
:name expr expr Create arm
;text Comment
"text" String (tape)
'text' String (cord)
~text Hint or exotic literal

Interfaces

Theoretically, a type is just a set of values. In practice, they do two things: they give you a convenient programming interface, and they stop you from running code. Loon gives you interfaces, but it doesn't use them to stop you from making mistakes. Type checking is done by an external linter rather than the language itself.

In the program above, {@path pax} is the entry point to a gate with one variable in its sample, pax, which is given the interface @path. @path is similar to @list, but its show method produces strings that look like paths rather than like lists. Elements of pax are granted the @ta interface unless they inherit a different interface at compile time, the way a wet gate does.

By inferring interfaces from a string literal, one can potentially use them to build statically-checked DSLs in Loon.

Hock + Loon = Holo

Holo is still in the design stage, but the tools in Hock and Loon (which, again, are already there in Nock and Hoon, waiting to be unlocked) are sufficient to create a concurrent, multithreaded operating system in the Martian fashion.