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.
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 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.
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.
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 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.
| 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 |
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.
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.