Idioms, Gates, and Design Patterns#
When one has spent enough time working with Nock, patterns emerge. While it’s unrealistic to simply sit and read a Nock program at a glance, it is very possible to recognize macroscopic structures that recur.
Gates#
Opcode 9 is built to recall a stored procedure, modify it in a characteristic way, and then invoke it. This pattern, called a “gate”, occurs frequently in Nock programs as the most convenient way to implement a function or a subroutine.
Because the gate is intended to be reused, it is stored with an opcode 1 so it can be retrieved and manipulated as a constant noun prior to execution.
For instance, this Nock program makes a gate that returns its argument incremented by 1. S is the subject it is run against:
[8 [1 0][0 S] [1 4 0 6] 0 1][0 S]: the result is the gate [[4 0 6] 0 S]In the gate [[4 0 6] 0 S], address 2 is the arm [4 0 6], 6 is the sample 0 and 7 is the context S.
One way to invoke it with an opcode 9 looks like this:
[7 [8 [1 0] [1 4 0 6] 0 1][[4 0 6] 0 S]: opcode 7 makes it the subject [8 [0 1][G G] [9 2[4 0 6] on [[4 0 6] 5 S] is 6 10 [6 7 [0 3] 1 5][0 3] is G, discarded by [1 5]) 0 2]][G G]]The meat of the pattern lies in the opcode 9 expression, which we may gloss as follows:
Retrieve the gate (using opcode 0).
Modify the gate by replacing the
0pin with the argument (using opcode 10).Invoke the modified gate.
You can expand it using the rules for opcodes 7, 8, and 9 (using 0 as an unused placeholder subject):
*[a 7 b c] *[*[a b] c]
*[a 8 b c] *[[*[a b] a] c]
*[a 9 b c] *[*[a c] 2 [0 1] 0 b]
Step by step, writing G for the gate [[4 0 6] 0 0] and G′ for the edited gate [[4 0 6] 5 0]:
*[0 7 [8 [1 0] [1 4 0 6] 0 1] 8 [0 1] 9 2 10 [6 7 [0 3] 1 5] 0 2]
rule 7: *[0 8 [1 0] [1 4 0 6] 0 1] = G, the new subject
*[G 8 [0 1] 9 2 10 [6 7 [0 3] 1 5] 0 2]
rule 8: push *[G 0 1] = G
*[[G G] 9 2 10 [6 7 [0 3] 1 5] 0 2]
rule 9: *[[G G] 10 [6 7 [0 3] 1 5] 0 2] = G′
*[G′ 2 [0 1] 0 2]
rule 2: run *[G′ 0 2] = [4 0 6] on *[G′ 0 1] = G′
*[G′ 4 0 6]
= 5 + 1 = 6
One way to think of Nock functions is as hygienic macros in the Lisp tradition. The gate pattern is a way to implement a macro that takes one argument and produces a new noun based on it.
Cores#
A gate is a specific instance of a more general pattern called a “core”. The purpose of a core is to collect a battery of code (even one gate) intended for reuse. Compare this code, which features two opcode 8 pins, to the prior increment gate:
[8 [1 A][A S], its arm at 2 [8 [9 2 0 1][[4 0 6] 0 A S]: subject [G A S] 9 2 10 [6 7 [0 3] 1 5] 0 2]][G A S] (the core [A S] is at 3)A = [8 [1 0] [1 4 0 6] 0 1]The arm A is the gate-building formula from before, so the gate it builds has the core [A S] as its context.
Its standard invocation uses opcode 9 twice: once to run the core’s arm, which builds the gate, and once to invoke the gate.
We can in fact distinguish two ways of storing data in a core: as code, using opcode 8 pins, and as data, using opcode 1 pins. This one, for instance, stores the number 42 as raw data inside the core:
[8 [1 [1 42] A][[1 42] A]: the core is [[[1 42] A] S] 8 [9 5 0 1][G core] 9 2 10 [6 7 [0 3] 1 5] 0 2]A = [8 [1 0] [1 4 0 6] 0 1]In the core, address 2 is the battery, 4 is the arm [1 42], 5 is the arm A and 3 is the payload S. Running arm 4 instead of building the gate returns the stored value: [8 [1 [1 42] 8 [1 0] [1 4 0 6] 0 1] 9 4 0 1] gives 42.
A core is a noun of the form [battery payload], where the battery is a noun that can be used to compute new cores, and the payload is an arbitrary noun. (It is worth reiterating at this point that Nock has no preference for this structure; it is simply a convention that Nock programmers use to organize their code.)