mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-11-29 23:12:08 +03:00
Merge pull request #37 from ab9/remove_old_doc
Remove old documentation
This commit is contained in:
commit
80a8bda300
@ -1,438 +0,0 @@
|
||||
Anatomy of Watt: 1, Molds
|
||||
|
||||
One of the errors most people make when they write their first
|
||||
few programming languages is to specify the language before they
|
||||
write the implementation. They then compound this error by
|
||||
designing the language before they specify it. And worst of all,
|
||||
they describe it before they design it.
|
||||
|
||||
Having made all these mistakes more than once myself, I was
|
||||
determined to avoid them in Watt. Fortunately, I now have a
|
||||
reasonably complete and working Watt, so I can describe it! The
|
||||
best way to learn Watt will always be to read the code, though.
|
||||
|
||||
[Note that this is a DRAFT description; Watt, still, does not
|
||||
exist. It will not even have a Kelvin number until it boots.
|
||||
Moreover, instead of "reading the code" in Watt, you have to read
|
||||
it in C and Yacc - both harder, and less rewarding. Also, I
|
||||
would avoid actually playing with the build for a couple of days
|
||||
- there is still some medium-scale surgery to be done. Besides,
|
||||
more actual lessons will follow.]
|
||||
|
||||
Watt is based on Nock, of course. Most specifically, Watt is
|
||||
defined as a pair of gates in the Watt kernel: the Watt grammar
|
||||
and the Mill composer.
|
||||
|
||||
:::::::: Watt: the grammar
|
||||
::
|
||||
- watt
|
||||
|| gene
|
||||
src = text
|
||||
==
|
||||
:: Parse Watt source.
|
||||
::
|
||||
[...]
|
||||
|
||||
:::::::: Mill: the composer
|
||||
::
|
||||
- mill
|
||||
|| [mol=mold noc=nock]
|
||||
sub = mold
|
||||
gen = gene
|
||||
==
|
||||
:: Generate Nock code and infer type.
|
||||
::
|
||||
[...]
|
||||
|
||||
Here at Moron Lab, we are of the total-immersion school of
|
||||
programming-language pedagogy. One of the many reasons it's
|
||||
foolish to write your language documentation before your language
|
||||
is that it's just impossible to keep your pseudocode up to date.
|
||||
|
||||
Therefore, let's dive into Watt with some straight-up code.
|
||||
Everything below is working Watt or my error. I swear.
|
||||
|
||||
So if the syntax above is not self-explanatory to you, here is
|
||||
a trivial decrement in Watt:
|
||||
|
||||
|| @
|
||||
z = @
|
||||
==
|
||||
:: Transform [z] to "(z - 1)"
|
||||
::
|
||||
=> a 0
|
||||
|-
|
||||
?: (eq z (inc a))
|
||||
a
|
||||
$(a (inc a))
|
||||
|
||||
The algorithm (which is O(n)) should be obvious on inspection.
|
||||
"@" is pronounced "atom."
|
||||
|
||||
We can also write Nock in Watt. Note that this code cheats: it
|
||||
delegates basic Nock functions to... Nock. The whole exercise
|
||||
is pointless, because we could express it as
|
||||
|
||||
|| *
|
||||
z = *
|
||||
==
|
||||
:3.z
|
||||
|
||||
Nonetheless, this code closely resembles the present spec and
|
||||
functions perfectly as written below. "*" is pronounced "blur,"
|
||||
meaning "any noun."
|
||||
|
||||
|| *
|
||||
z = *
|
||||
==
|
||||
:: Compute "Nock(a)" - 9K.
|
||||
::
|
||||
?- z
|
||||
- [a 0 b=@]
|
||||
$(z :3.[a.z 0 b.z]) :: cheating
|
||||
|
||||
- [a 1 b=*]
|
||||
b
|
||||
|
||||
- [a 2 b=* c=* d=*]
|
||||
$(z [a.z 3 [0 1] 3 [1 c.z d.z] [1 0] 3 [1 2 3] [1 0] 5 5 b.z])
|
||||
|
||||
- [a 3 b=*]
|
||||
$(z $(z [a.z b.z]))
|
||||
|
||||
- [a 4 b=*]
|
||||
$(z :3.[a.z 4 b.z]) :: cheating
|
||||
|
||||
- [a 5 b=*]
|
||||
$(z :3.[a.z 5 b.z]) :: cheating
|
||||
|
||||
- [a 6 b=*]
|
||||
$(z :3.[a.z 6 b.z]) :: cheating
|
||||
|
||||
- [[a=* b=*] c=*]
|
||||
[$(z [a.z b.z]) $(z c.z)]
|
||||
==
|
||||
|
||||
We could also, of course, write:
|
||||
|
||||
- [a 2 b=* c=* d=*]
|
||||
?: $(z [a.z b.z])
|
||||
$(z a.z c.z)
|
||||
$(z a.z d.z)
|
||||
|
||||
Which should give the semantics of 2 away!
|
||||
|
||||
But this is an entirely intuitive introduction. It should under
|
||||
no circumstances be mistaken for a definition, or even a tutorial.
|
||||
It is just meant to explain what I mean when I use Watt syntax,
|
||||
in a paralinguistic Rosetta-Stone sort of way.
|
||||
|
||||
To understand Watt in a concrete way, let's see those gates again:
|
||||
|
||||
:::::::: Watt: the reader (ie, syntax)
|
||||
::
|
||||
- watt
|
||||
|| gene
|
||||
src = text
|
||||
==
|
||||
:: Parse source.
|
||||
::
|
||||
[...]
|
||||
|
||||
:::::::: Mill: the composer (ie, compiler)
|
||||
::
|
||||
- mill
|
||||
|| [mol=mold noc=nock]
|
||||
sub = mold
|
||||
gen = gene
|
||||
==
|
||||
:: Generate code and infer type.
|
||||
::
|
||||
[...]
|
||||
|
||||
In programmer-English, Watt is a function that transforms a text
|
||||
file (as a Nock atom) into a Watt AST (as a Nock noun). Mill is
|
||||
a function that transforms a Watt type (again a Nock noun, of
|
||||
course) into a Watt type and a Nock formula.
|
||||
|
||||
A Watt type is called a "mold." The abstract syntax tree (ie,
|
||||
your code read in, with minimal processing, as a data structure)
|
||||
is called a "gene."
|
||||
|
||||
(Why this unusual language? Why not call a type a type? Because,
|
||||
as a programmer, the word "type" already means something to you.
|
||||
Moreover, there is actually more than one concept in Watt for
|
||||
which we might use the word "type." It's entirely possible that
|
||||
people will just end up calling molds "types," but anyone you see
|
||||
instantiating this meme should be vigorously chastised! As for
|
||||
the superiority of "gene" over "AST", I will admit no argument.)
|
||||
|
||||
Mill is a function that transforms an ordered pair of
|
||||
two nouns, [sub gen], to another ordered pair, [mol noc].
|
||||
|
||||
Let's look at these mysterious nouns, the molds. Here is the
|
||||
definition of a mold:
|
||||
|
||||
:::::::: mold
|
||||
::
|
||||
- mold
|
||||
|~
|
||||
**
|
||||
%atom
|
||||
%blot
|
||||
%blur
|
||||
[%cell p=mold q=mold]
|
||||
[%cone p=mold q=(bush mark gene)]
|
||||
[%cube p=*]
|
||||
[%face p=mark q=mold]
|
||||
[%fork p=mold q=mold]
|
||||
[%fuse p=mold q=mold]
|
||||
[%hold p=mold q=gene]
|
||||
==
|
||||
|
||||
Peculiarly enough, this gate is both the definition of a mold and
|
||||
its implementation. For instance, if you have a random noun, n,
|
||||
which you're hoping might be a mold,
|
||||
|
||||
(mold n)
|
||||
|
||||
will produce n if n is a mold, and exit if n is not a mold. The
|
||||
result, of course, will be in mold [mold] - ie, it will be typed.
|
||||
(We say "in mold foo" instead of "of type foo.")
|
||||
|
||||
This is called "rectifying" n. Rectification is quite useful in
|
||||
the context of network communication - it is common to receive
|
||||
generic, unvalidated data from foreign sources, and need to
|
||||
import it into a strictly-typed programming language, a task
|
||||
commonly done ad hoc or not at all.
|
||||
|
||||
But I digress. Let's describe the type system - I mean, the
|
||||
mold system.
|
||||
|
||||
First, the philosophy of this system is the same minimalism
|
||||
you've already seen in Nock. Unlike Nock, Watt is not quite
|
||||
complete - there are a few bells and whistles I want to add,
|
||||
which will result in two or three more forks of mold. However,
|
||||
the definition above is sufficient for quite a rich programming
|
||||
experience. The bells and whistles will make rectification more
|
||||
powerful and facilitate object-oriented programming. Even with
|
||||
the above, however, OOP is doable - just a little "ghetto."
|
||||
|
||||
Let's walk through this data structure very quickly, and we'll
|
||||
keep your introduction to Watt short and easy to chew.
|
||||
|
||||
A mold satisfies the two basic requirements of any type or data
|
||||
description system.
|
||||
|
||||
One: a mold unambiguously defines a set of nouns. For any mold and
|
||||
any noun, that noun is either in the mold or not in the mold.
|
||||
Without the bells and whistles, we cannot describe all such sets,
|
||||
but even without the bells and whistles we can express just about
|
||||
any practical data structure.
|
||||
|
||||
(For instance, a mold is to nouns much as an XML DTD or schema is
|
||||
to valid XML. The difference is that nouns are much simpler than
|
||||
XML; molds are much, much, much simpler than XML Schema; and XML
|
||||
does not ship with a functional programming language. So it's a
|
||||
little like comparing an oxcart to a Ducati. But both are
|
||||
perfectly effective ways of getting from point A to point B.)
|
||||
|
||||
Two: a mold ascribes semantics to nouns in the mold. More
|
||||
precisely, it defines a namespace on the noun. A name in Watt is
|
||||
called a "port"; a path is a "wire." When you see [a.z] above,
|
||||
this is referencing port "a" in port "z" in the subject. (Note
|
||||
that in most Earth programming languages, this would be "z.a".)
|
||||
[$] is simply the name 0 - sometimes a name is superfluous.
|
||||
|
||||
Ports can be fragments of the subject ("parts") or computed
|
||||
functions ("hooks"). You can also hack into a noun with a direct
|
||||
fragment address - eg, [+2] for the head, [+3] for the tail.
|
||||
|
||||
Lisp programmers learning Watt may be slightly puzzled by the
|
||||
lack of an environment. Indeed, the simplest way to describe
|
||||
Watt to someone who knows Lisp is: Lisp without an environment.
|
||||
|
||||
Instead, every Watt gene is evaluated relative to a single noun
|
||||
which contains all data *and code* available to that gene. This
|
||||
is called "the subject." Thus, when we compute
|
||||
|
||||
(mill mol gen)
|
||||
|
||||
and produce a product
|
||||
|
||||
[pro=[mol=mold noc=nock]]
|
||||
|
||||
we are saying: produce the Nock formula, [noc.pro], that computes
|
||||
the function defined by [gen] on a subject in mold [mol]. The
|
||||
result of this function will be in mold [mol.pro].
|
||||
|
||||
Let's list the mold forks quickly, and we'll be done with this
|
||||
lesson. Frankly, my previous essays have been too long. There
|
||||
are two kinds of mold: the obvious kind, and the non-obvious
|
||||
kind. First, the former.
|
||||
|
||||
|
||||
Three atomic molds: %atom, %blot, %blur. %atom means: any
|
||||
atom. %blur means: any noun. %blot means: no nouns (ie,
|
||||
"void"). I trust there is no rocket science here. None of these
|
||||
export any ports.
|
||||
|
||||
[%cube p=*]: a constant noun. Any noun that equals [p] is in the
|
||||
mold; any noun that does not, is not. No ports are exported.
|
||||
|
||||
[%cell p=mold q=mold]: a cell, ie, ordered pair. [p] is the mold
|
||||
of the head; [q] the mold of the tail. Exports all ports of [p]
|
||||
and [q]; any conflict is an error.
|
||||
|
||||
[%fork p=mold q=mold]: set union of the molds [p] and [q].
|
||||
Exports any port for which [p] and [q] define the same formula.
|
||||
Symmetric; [%fork q p] has the same semantics as [%fork p q].
|
||||
|
||||
[%fuse p=mold q=mold]: set intersection of the molds [p] and [q].
|
||||
Asymmetric; [q] has higher priority, ie, [p] cannot change or
|
||||
conceal the semantics of [q].
|
||||
|
||||
[%face p=mark q=mold]: name a part. Exports the name [p]. For
|
||||
ports in [q], read through [p] - eg, in the Nock example above,
|
||||
[a.z] works but [a] will give you "port not found." [p] may not
|
||||
be 0 - port 0 is always a hook, never a part.
|
||||
|
||||
Now, the above are all the *obvious* molds. If you were writing
|
||||
a simple data-description model, the noun equivalent of a DTD,
|
||||
for finite static nouns, they might even be sufficient. But for
|
||||
actual functional programming, we need two more: %cone and %hold.
|
||||
If your brain has been nodding "duh, duh, of course" up to this
|
||||
point, it's time for it to sit up and pay attention!
|
||||
|
||||
|
||||
[%cone p=mold q=(bush mark gene)]: executable code. A bush is
|
||||
simply a data structure which, like Nock formulas, overloads the
|
||||
head atom: it is either
|
||||
|
||||
[mark gene]
|
||||
|
||||
mark being an atom - ie, a symbol - or
|
||||
|
||||
[(bush mark gene) (bush mark gene)]
|
||||
|
||||
with a tree search order. Thus, what we're looking at here is...
|
||||
a symbol table. Very comparable to a vtable in C++. Okay, we
|
||||
may still be in "duh" territory.
|
||||
|
||||
The nouns in the cone are of the form [data code]. [data]
|
||||
is a noun in the mold [p]. [code] matches the geometry of [q];
|
||||
every leaf
|
||||
|
||||
[mark gene]
|
||||
|
||||
becomes the Nock formula
|
||||
|
||||
noc.(mill [%cone p q] gene)
|
||||
|
||||
A cone exports all the marks in its book as hooks. If the port
|
||||
equals the mark, the formula produced calls the hook formula,
|
||||
with a subject that is the entire cone (not just the cone head).
|
||||
|
||||
So, for instance, the Nock equivalent of a lambda is a "gate."
|
||||
This is always a mold of the form
|
||||
|
||||
[%cone
|
||||
[%cell arg=mold env=mold]
|
||||
[0 gene]
|
||||
]
|
||||
|
||||
Ie, the data in a gate is an argument-environment pair (so there
|
||||
*is* an environment - it's just reentrant), and a hook whose name
|
||||
is 0 (ie, [$]). To invoke the lambda, replace the default
|
||||
argument with a compatible value of your choice, and use the
|
||||
hook.
|
||||
|
||||
When you write
|
||||
|
||||
(dec x)
|
||||
|
||||
you are actually invoking *two* hooks. First, [dec] resolves to
|
||||
a hook in the kernel which produces a decrement gate. Second,
|
||||
you replace the argument [+4] in that gate with [arg], and invoke
|
||||
the formula.
|
||||
|
||||
Note, in particular, that Watt never calculates "type signatures"
|
||||
for code. Rather, the gene is simply embedded in the mold! And
|
||||
since different genes generate different formulas, cones with
|
||||
different books are never in any sense compatible. If this makes
|
||||
you wonder how we do higher-order polymorphism, it should!
|
||||
Suffice it to say: there's a trick.
|
||||
|
||||
|
||||
[%hold p=mold q=gene]: delayed evaluation. For all but cosmetic
|
||||
purposes, [%hold p q] is equivalent to
|
||||
|
||||
mol.(mill p q)
|
||||
|
||||
Ie, [%hold p q] is the mold produced by gene [q] in mold [p].
|
||||
|
||||
Some programming languages support infinite data structures.
|
||||
Watt is not one of these; all Watt data structures are Nock
|
||||
nouns, and all Nock nouns are finite.
|
||||
|
||||
However, we often find ourselves needing to describe *indefinite*
|
||||
data structures. For example, a list of pairs of atoms:
|
||||
|
||||
(list :[@ @])
|
||||
|
||||
Nouns in this mold include
|
||||
|
||||
0
|
||||
[[3 7] 0]
|
||||
[[2 3] [37 42] 0]
|
||||
|
||||
And so on. Clearly, a list can be as long as you want. Clearly,
|
||||
since molds are Nock nouns, and Nock nouns are finite, we must
|
||||
define this infinitely-large set, containing indefinitely-large
|
||||
nouns, with a single mold which is a finite noun. Yikes!
|
||||
|
||||
Nor is [list] a built-in feature. If you scroll back up to the
|
||||
mold definition, you will not see any mention of lists - or any
|
||||
other generic data structure. Actually, [list] is just a gate
|
||||
in the kernel, like [dec] or [add] or [mill].
|
||||
|
||||
So when I write
|
||||
|
||||
((list :[@ @]) [[2 3] [37 42] 0])
|
||||
|
||||
ie, rectifying this noun, just as we might write
|
||||
|
||||
(mold [%cell %atom [%fork [%cube 12] [%cube 32]]])
|
||||
|
||||
what is the product mold? We'll see the answer in the next
|
||||
lesson. But it can only involve %hold.
|
||||
|
||||
In Watt, a finite and strict language, %hold is essentially lazy
|
||||
evaluation done manually. A list mold is *logically* infinite -
|
||||
that is, if you keep evaluating the holds, you can evaluate them
|
||||
ad infinitum. If Watt was implemented in Haskell, this would be
|
||||
done automatically rather than manually, and molds would be an
|
||||
infinite/lazy data structure.
|
||||
|
||||
But Watt is defined in Watt, not Haskell. (Well, it should
|
||||
be defined in Watt. Right now, of course, it's in C.) So
|
||||
lazy evaluation happens manually - we expand a %hold when we need
|
||||
to, and not otherwise.
|
||||
|
||||
A crucial property of Watt that makes this work is that all molds
|
||||
are *rational*: that is, we can expand them indefinitely, but at
|
||||
a certain point they always repeat themselves. This allows us to
|
||||
compute the obvious set-theoretic functions needed for any type
|
||||
system, such as mold compatibility and orthogonality, without
|
||||
going into an infinite loop. It is possible to define Watt genes
|
||||
for which Mill is very slow, but it is not possible to define
|
||||
Watt genes for which it goes into an infinite loop. Or, at
|
||||
least, if it is, there's a bug in my code!
|
||||
|
||||
All right. I think this is enough for one lesson.
|
||||
|
||||
Here is a concrete problem to cogitate on: how would you write a
|
||||
function that tests whether molds A and B are orthogonal (no noun
|
||||
is in both A and B)? Or compatible (any noun in A is also in B)?
|
||||
Are these functions NP-complete? If so, how might one work
|
||||
around this in practice?
|
@ -1,404 +0,0 @@
|
||||
Anatomy of Watt: 2, Genes
|
||||
|
||||
Watt now works - or at least, works well enough to implement a
|
||||
polymorphic quicksort gate with parameterized comparator. It can
|
||||
also define its own implementation data structures, although
|
||||
these are slightly underconstrained. You'll find these in
|
||||
Test/qsort.watt and Test/mill.watt respectively.
|
||||
|
||||
I will prepare something like a tutorial introduction soon. I
|
||||
would hold off on actually playing with Watt until this is
|
||||
available, though of course you can. For now, I want to finish
|
||||
understanding Watt the way it's meant to be understood. There is
|
||||
no part 3 of this essay! After "Anatomy of Watt," the rest is
|
||||
just a bunch of tedious memorization - like learning any
|
||||
language.
|
||||
|
||||
Basically, there are two kinds of languages. There are languages
|
||||
meant to be learned from the syntax down, and languages meant to
|
||||
be learned from the plumbing up.
|
||||
|
||||
Haskell is an example of the former. I have never seen anyone
|
||||
try to teach Haskell by explaining its internals. In fact, last
|
||||
time I looked, the Haskell type-inference algorithm was not
|
||||
even explained in the Haskell documentation. As in: when I tried
|
||||
to look it up, I got redirected to a '70s academic paper behind a
|
||||
paywall. The idea of Haskell is: you do enough tutorials, and
|
||||
eventually you'll just get it. I suppose this works for some.
|
||||
It makes me want to throw a professor through some drywall.
|
||||
|
||||
C is an example of the latter. To learn C, you learn what the C
|
||||
compiler is doing. This is only viable because the C compiler,
|
||||
at least in the abstract, is a terribly simple bit of code. C is
|
||||
really only one step beyond a macro assembler.
|
||||
|
||||
Watt, while not at all like C, has much the same relationship
|
||||
to Nock that C has to assembly language. The semantic core of
|
||||
Watt is quite straightforward. Mapping this core onto a textual
|
||||
language which doesn't look or feel like ass is a good bit more
|
||||
work; and even when this work is done, the machinery is not at
|
||||
all hidden. It can't be hidden. IMHO, it shouldn't be.
|
||||
|
||||
In the first part of "Anatomy," we discussed the mold system,
|
||||
which you'll see at the top of mill.watt. Today, we're going to
|
||||
look at the gene system, which is the rest of the file.
|
||||
Basically, molds are types and genes are code.
|
||||
|
||||
Recall that the basic toolchain in Watt is
|
||||
|
||||
- mill
|
||||
|| [mol=mold noc=nock]
|
||||
sub = mold
|
||||
gen = gene
|
||||
==
|
||||
|
||||
so, when you want to compile something,
|
||||
|
||||
(mill sub gen)
|
||||
|
||||
where [sub] is the subject type and [gen] is the Watt code,
|
||||
produces [mol noc], where [mol] is the product type and [noc] is
|
||||
the Nock formula. Rockit science, this is not.
|
||||
|
||||
A key point to remember: Nock has no global environment. Whether
|
||||
it's code or data, if it's not in the subject, it doesn't exist.
|
||||
|
||||
Now, the gene part of mill.watt is a lot bigger than the mold
|
||||
part. It is also a lot scarier - it includes a number of
|
||||
strange, four-letter symbols which may be Martian curse words.
|
||||
|
||||
However, like the mold system, the gene system is actually pretty
|
||||
simple. Almost all these stems are internal macros - that is,
|
||||
they are implemented simply by expansion. We could in fact
|
||||
expand them in the parser, although this would make for some
|
||||
pretty husky genes.
|
||||
|
||||
While a few more will probably be added, at present the only
|
||||
axiomatic stems are the initial block of 15, from %bail through
|
||||
%spot:
|
||||
|
||||
[%bail ~] :: !!
|
||||
[%cast fes=gene rum=gene] :: ^-
|
||||
[%cage fut=(list gene)] :: +-
|
||||
[%dbug lyq=gene] :: !#
|
||||
[%home cux=home lyq=gene] :: !:
|
||||
[%kick ved=wire suc=bolt] :: :=
|
||||
[%like rid=wire bul=gene] :: ?=
|
||||
[%link del=gene zim=gene] :: ~>
|
||||
[%load pir=book] :: |@
|
||||
[%name tem=mark caf=gene] :: +=
|
||||
[%nock fiz={3 4 5 6} dil=gene] :: :N
|
||||
[%quiz tes=gene bif=gene hob=gene] :: ?:
|
||||
[%rock gos=*] :: %=
|
||||
[%sure fes=gene gav=gene vep=gene] :: ^=
|
||||
[%spot xed=spot lyq=gene] :: !@
|
||||
|
||||
|
||||
Moreover, three of these (%dbug, %home, and %spot) are
|
||||
exclusively for tracing and debugging. This leaves us with just
|
||||
12 stems to explain.
|
||||
|
||||
(A noun of the above form is called a "rose." The head, or tag,
|
||||
is the "stem." The tail, or payload, is the "bulb." An atomic
|
||||
leaf is a "bud." An implicit pair, like the implicit cons in
|
||||
Nock, is a "twig." Your form can have either buds or twigs, but
|
||||
not of course both.)
|
||||
|
||||
Let's go through them in non-alphabetical order, from simplest
|
||||
to trickiest.
|
||||
|
||||
[%bail ~]
|
||||
|
||||
%bail: bail out.
|
||||
|
||||
Formally, in the Nock spec, this is an infinite loop. In
|
||||
practice it just returns you to the command line.
|
||||
|
||||
Produces
|
||||
++
|
||||
mol=%blot
|
||||
noc=[0 0]
|
||||
==
|
||||
|
||||
|
||||
[%rock gos=*]
|
||||
|
||||
%rock: specific constant.
|
||||
|
||||
Produces
|
||||
++
|
||||
mol = [%cube gos]
|
||||
noc = [1 gos]
|
||||
==
|
||||
|
||||
|
||||
[%nock fiz={3 4 5 6} dil=gene]
|
||||
|
||||
%nock: Nock operator.
|
||||
|
||||
Produces
|
||||
++
|
||||
mol =
|
||||
?- fiz
|
||||
- 3
|
||||
%blur
|
||||
- 4
|
||||
[%fork [%cube 0] [%cube 1]]
|
||||
- 5
|
||||
%atom
|
||||
- 6
|
||||
[%fork [%cube 0] [%cube 1]]
|
||||
==
|
||||
noc = [fiz noc.(mill sub dil)]
|
||||
==
|
||||
|
||||
|
||||
[%cage fut=(list gene)]
|
||||
|
||||
%cage: construct tuple.
|
||||
|
||||
This is Lisp cons - n-ary rather than binary, to reduce gene
|
||||
size. Eg, for gen=[%cage foo bar ~], we get
|
||||
|
||||
++
|
||||
mol = [%cell mol.(mill sub foo) mol.(mill sub bar)]
|
||||
noc = [noc.(mill sub foo) noc.(mill sub bar)]
|
||||
==
|
||||
|
||||
|
||||
[%link del=gene zim=gene]
|
||||
|
||||
%link: compose functions.
|
||||
|
||||
Produces
|
||||
++
|
||||
mol = mol.(mill mol.(mill sub del) zim)
|
||||
noc = [3 noc.(mill sub del)
|
||||
1 noc.(mill mol.(mill sub del) zim)
|
||||
]
|
||||
==
|
||||
|
||||
ie, in C syntax, zim(del(sub)).
|
||||
|
||||
|
||||
[%name tem=mark caf=gene]
|
||||
|
||||
%name: name data.
|
||||
|
||||
Produces
|
||||
++
|
||||
mol = [%face tem mol.(mill sub caf)]
|
||||
noc = noc.(mill sub caf)
|
||||
==
|
||||
|
||||
|
||||
[%cast fes=gene rum=gene]
|
||||
|
||||
%cast: mold conversion.
|
||||
|
||||
Use mold of [fes], nock of [rum]:
|
||||
|
||||
++
|
||||
mol = mol.(mill sub fes)
|
||||
noc = noc.(mill sub rum)
|
||||
==
|
||||
|
||||
given the congruence test
|
||||
|
||||
(nest mol.(mill sub rum) mol.(mill sub fes))
|
||||
|
||||
where (nest foo bar) is yes only if every noun in mold [foo]
|
||||
is also in mold [bar]. ("Only if," not "if and only if,"
|
||||
meaning that the test is conservative.)
|
||||
|
||||
|
||||
[%sure fes=gene gav=gene vep=gene]
|
||||
|
||||
%sure: congruence assertion.
|
||||
|
||||
Produces
|
||||
|
||||
(noc mol vep)
|
||||
|
||||
given the congruence test
|
||||
|
||||
(nest mol.(mill sub gav) mol.(mill sub fes))
|
||||
|
||||
See %cast for the semantics of (nest).
|
||||
|
||||
|
||||
[%quiz tes=gene bif=gene hob=gene]
|
||||
|
||||
%quiz: if-then-else
|
||||
|
||||
Produces (oversimplifying somewhat)
|
||||
++
|
||||
mol = [%fork mol.(mill sub bif) mol.(mill sub hob)]
|
||||
noc = [2
|
||||
noc.(mill sub tes)
|
||||
noc.(mill sub bif)
|
||||
noc.(mill sub hob)
|
||||
]
|
||||
--
|
||||
|
||||
%quiz analyzes the boolean structure of [tes] for any
|
||||
%like genes which specialize the subject. If such
|
||||
a specialization mold [pax] is produced, it is fused with
|
||||
[sub] for the [bif] branch - ie,
|
||||
|
||||
(mill [%fuse pax sub] bif)
|
||||
|
||||
|
||||
[%like rid=wire bul=gene]
|
||||
|
||||
%like: test mold membership
|
||||
|
||||
%like tests whether a fragment of the subject, designated by
|
||||
[rid], is in the mold produced by the subject and [bul]. The
|
||||
result is a standard Nock loobean: 0 for yes, 1 for no.
|
||||
|
||||
[rid] must be a "flat" wire (see part 1 for the definition of
|
||||
[wire], or see the description of %kick below), meaning
|
||||
only parts, no hooks - ie, it must reference a fragment of
|
||||
the subject, not a functional pseudo-attribute.
|
||||
|
||||
[bul] must be a simple, flat mold - %like will not generate
|
||||
code for any old [bul]. Recursive molds, cones, etc, are not
|
||||
allowed - as a matter of policy, rather than ability.
|
||||
Rather, it is used only for simple disambiguation between
|
||||
forks, eg, matching stems.
|
||||
|
||||
See %quiz for how %like is used to specialize type.
|
||||
|
||||
|
||||
[%load pir=book]
|
||||
|
||||
%load: load code
|
||||
|
||||
%load creates a cone, which is a [data code] pair. [pir],
|
||||
the book, is a bush: either [mark gene] or [book book].
|
||||
This is translated into Nock code with the same geometry.
|
||||
[mark gene] becomes [nock]; [[mark gene] [mark gene]]
|
||||
becomes [nock nock]; and so on.
|
||||
|
||||
The subject for these formulas is always the cone itself.
|
||||
Mill does not compute type signatures; rather, the cone
|
||||
mold simply preserves the entire book.
|
||||
|
||||
Thus, (mill sub [%load [%myname mycode]]) generates
|
||||
|
||||
++
|
||||
[%cone sub [%myname mycode]]
|
||||
noc.(mill [%cone sub [%myname mycode]] mycode)
|
||||
--
|
||||
|
||||
|
||||
[%kick ved=wire suc=bolt]
|
||||
|
||||
%kick: use with changes
|
||||
|
||||
%kick is our last and most difficult stem. You might even
|
||||
say that all other kinds of gene are the supporting cast for
|
||||
%kick. Basically, Watt *is* %kick.
|
||||
|
||||
Basically, [ved] is a part or hook within the subject, and
|
||||
[suc] is a list of changes to the subject.
|
||||
|
||||
Recall the definitions of (wire) and (bolt):
|
||||
|
||||
- port
|
||||
|~
|
||||
**
|
||||
@:mark
|
||||
[%find tic=step vix=mark]
|
||||
[%frag haz=axis]
|
||||
==
|
||||
|
||||
- wire
|
||||
(list port)
|
||||
|
||||
- bolt
|
||||
(list :[p=wire q=gene])
|
||||
|
||||
So it looks like there are three kinds of ports. But there
|
||||
are only two: %find and %frag. To reduce gene size,
|
||||
|
||||
[%find 0 vix]
|
||||
|
||||
can be expressed as just [vix].
|
||||
|
||||
%frag maps directly to the Nock fragment operator, 0. %find
|
||||
searches for either a %face mold with a matching mark, or a
|
||||
cone with a matching mark in its book. If [tic] is nonzero,
|
||||
it searches into that mark or in the cone data with (dec
|
||||
tic). Otherwise, it either returns the face fragment matching
|
||||
the mark, or invokes the cone formula matching the mark. In
|
||||
the latter case, again, the subject is the cone itself.
|
||||
|
||||
If [suc] is nonzero, it specifies a list of edits to be
|
||||
applied to either the fragment or the cone subject. So, in
|
||||
the latter case, we edit the data and call the code. This
|
||||
can produce very strange and powerful effects.
|
||||
|
||||
A simple example of these strange and powerful effects is
|
||||
the common, or garden, function call. In Watt terms, we are
|
||||
kicking a gate. Suppose, for instance, we call
|
||||
|
||||
(add foo bar)
|
||||
|
||||
This does exactly what it might do in, say, Lisp: it produces
|
||||
the sum of foo and bar. Indeed I have shamelessly borrowed
|
||||
the Lisp syntax. But Watt is not, after all, a lambda
|
||||
language - so what happens behind the scenes is much
|
||||
stranger.
|
||||
|
||||
When the parser parses (add foo bar), it becomes the macro
|
||||
|
||||
[%mang
|
||||
[%kick [%add ~] ~]
|
||||
[%cage
|
||||
[%kick [%foo ~] ~]
|
||||
[%kick [%bar ~] ~]
|
||||
~
|
||||
]
|
||||
]
|
||||
|
||||
%mang means "call." This expands to a somewhat hefty macro
|
||||
which I won't burden you with at present. Let's look at what
|
||||
it does, though.
|
||||
|
||||
First we need to resolve %add. This is a hook in the kernel.
|
||||
But the hook does not actually add - rather, it produces a
|
||||
"gate," whose mold will be of the form
|
||||
|
||||
[%cone
|
||||
[%cell arg=mold env=mold]
|
||||
[0 add-gene]
|
||||
]
|
||||
|
||||
Thus, a gate is a cone whose data is a pair [arg env], and
|
||||
whose code is one nameless gene (since there's only one, a
|
||||
name would be superfluous). In the case of this particular
|
||||
gate, [arg] is the default argument for add - [0 0], and
|
||||
[env] is the aforementioned kernel, a cone whose book is all
|
||||
the gates in Cato/k300.watt, and whose data is ~, constant 0.
|
||||
|
||||
In order to call this add function on [foo bar], therefore,
|
||||
we need to (a) edit the gate to replace the default argument
|
||||
with [foo bar], and (b) invoke hook 0. Hence:
|
||||
|
||||
[%kick
|
||||
[0 0] :: kick hook 0
|
||||
[ [+4 :: changing fragment 4 - arg
|
||||
[%cage ;; to [foo bar]
|
||||
[%kick [%foo 0] 0]
|
||||
[%kick [%bar 0] 0]
|
||||
0
|
||||
]
|
||||
]
|
||||
0
|
||||
]
|
||||
]
|
||||
|
||||
After a while, this will feel completely natural to you - and
|
||||
lambda reduction will seem like bizarre alien technology.
|
File diff suppressed because one or more lines are too long
3004
Spec/watt/297.watt
3004
Spec/watt/297.watt
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
2982
Spec/watt/298.watt
2982
Spec/watt/298.watt
File diff suppressed because it is too large
Load Diff
@ -1,42 +0,0 @@
|
||||
slon ~> X [del=gene zim=gene] [zim] in [del]
|
||||
gant := X [ved=rope suc=list+[rope gene]] use with changes
|
||||
blin |* E [bov=gene pir=list+gene] load and apply
|
||||
boce ?& E [das=list+gene] yes iff all of [das] are yes
|
||||
perd ^+ X [fes=gene rum=gene] [rum] perd to [fes], geometric
|
||||
brip :^ E [mef=skel] skeleton lamb
|
||||
halc ^- X [fes=gene rum=gene] [rum] perd to [fes], genetic
|
||||
crot ^@ E [rum=gene] [rum] perd to bead
|
||||
dbug !? X [lyq=gene] activate internal debug
|
||||
delc :$ E [mef=skel] skeleton stub
|
||||
feng ?> E [tes=gene bif=gene] if [tes], then [bif], else fail
|
||||
flec :- E [buz=gene] fire with default
|
||||
flot |- E [cug=gene] hot lamb
|
||||
frez &~ E [dil=gene] pray
|
||||
garc :+ E [buz=gene pum=list+gene] fire with list
|
||||
glax ?+ E [feg=gene bem=list+pike] examine [feg], externally
|
||||
glem -> E [hig=gene muc=gene] [muc] in [hig +]
|
||||
clep X [pir=bush+[mark gene]] load book
|
||||
tarn ^= X [fes=gene gav=gene vep=gene] [vep], since [gav] matches [fes]
|
||||
gult X [fiz=(3 4 5 6 7 8) dil=gene] nock operator
|
||||
stol ?: X [tes=gene bif=gene hob=gene] if [tes], then [bif], else [hob]
|
||||
lisc -< E [hig=gene muc=gene] [hig] in [muc +]
|
||||
lome |= E [cug=gene] cold lamb
|
||||
sard :* X [caw=list+[mark gene]] classic American tuple
|
||||
malk |+ E [pir=list+[mark gene]] book as list
|
||||
neft &^ E [dil=gene] inc
|
||||
peld ?| E [das=list+gene] yes iff any of [das] are yes
|
||||
pont => E [han=mark dur=gene leb=gene] [leb] in [[han=dur] +]
|
||||
prex ?< E [tes=gene hob=gene] if [tes], then fail, else [hob]
|
||||
pung :~ E [buz=gene lep=gene] fire lamb, direct
|
||||
rald ^* E [rum=gene] [rum] perd to blur
|
||||
rulf =< E [han=mark dur=gene leb=gene] [dur] in [[han=leb] +]
|
||||
spot X [xed=[[@ @] [@ @] lyq=gene] source position, [at to] [lin col]
|
||||
serd |: E [tep=gene von=gene] lamb construction
|
||||
poos X [cux=lab lyq=gene] source label
|
||||
spal &~ E [dil=gene] cell
|
||||
stiv ~< E [del=gene zim=gene] [del] in [zim]
|
||||
trup ?! E [gix=gene] not
|
||||
veck &* E [dil=gene] raw eval
|
||||
forb ?= X [rid=rope bul=gene] yes iff [rid] is like [bul]
|
||||
wamp &= E [dil=gene] eq
|
||||
zact ?- E [mox=rope bem=list+pike] examine [mox], internally
|
@ -1,31 +0,0 @@
|
||||
blin |* E [bov=gene pir=list+[mark gene]] load and apply
|
||||
boce ?& E [das=list+gene] yes iff all of [das] are yes
|
||||
brip :^ E [mef=skel] skeleton lamb
|
||||
crot ^@ E [rum=gene] [rum] perd to bead
|
||||
delc :$ E [mef=skel] skeleton stub
|
||||
drol || E [nix=skel gub=skel von=gene] lamb with cast
|
||||
feng ?> E [tes=gene bif=gene] if [tes], then [bif], else fail
|
||||
flec :- E [buz=gene] call with default
|
||||
flot |- E [cug=gene] hot lamb
|
||||
frez &~ E [dil=gene] pray
|
||||
garc :+ E [buz=gene pum=list+gene] call with list
|
||||
glax ?+ E [feg=gene bem=list+pike] examine [feg], externally
|
||||
glem -> E [hig=gene muc=gene] [muc] in [hig +]
|
||||
lisc -< E [hig=gene muc=gene] [hig] in [muc +]
|
||||
lome |= E [cug=gene] cold lamb
|
||||
malk |+ E [pir=list+[mark gene]] book as list
|
||||
neft &^ E [dil=gene] inc
|
||||
peld ?| E [das=list+gene] yes iff any of [das] are yes
|
||||
plom :. E [fut=list+gene] nameless tuple
|
||||
pont => E [han=mark dur=gene leb=gene] [leb] in [[han=dur] +]
|
||||
prex ?< E [tes=gene hob=gene] if [tes], then fail, else [hob]
|
||||
pung :~ E [buz=gene lep=gene] call lamb, direct
|
||||
rald ^* E [rum=gene] [rum] perd to blur
|
||||
rulf =< E [han=mark dur=gene leb=gene] [dur] in [[han=leb] +]
|
||||
stur |: E [tep=skel von=gene] lamb construction
|
||||
spal &~ E [dil=gene] cell
|
||||
stiv ~< E [del=gene zim=gene] [del] in [zim]
|
||||
trup ?! E [gix=gene] not
|
||||
veck &* E [dil=gene] raw eval
|
||||
wamp &= E [dil=gene] eq
|
||||
zact ?- E [mox=rope bem=list+pike] examine [mox], internally
|
@ -1,15 +0,0 @@
|
||||
clep X [pir=bush+[mark gene]] load book
|
||||
dbug !? X [lyq=gene] activate internal debug
|
||||
forb ?= X [rid=rope bul=gene] yes iff [rid] is like [bul]
|
||||
gant := X [ved=rope suc=list+[rope gene]] use with changes
|
||||
gult X [fiz=(3 4 5 6 7) dil=gene] nock operator
|
||||
griv && X [dil=gene zep=noun] raw formula
|
||||
halc ^- X [fes=gene rum=gene] [rum] perd to [fes], genetic
|
||||
perd ^+ X [fes=gene rum=gene] [rum] perd to [fes], geometric
|
||||
poos X [cux=lab lyq=gene] source label
|
||||
pret X [daq=clod] constant, cube
|
||||
sard :* X [caw=list+[mark gene]] classic American tuple
|
||||
slon ~> X [del=gene zim=gene] [zim] in [del]
|
||||
spot X [xed=[[@ @] [@ @] lyq=gene] source position, [at to] [lin col]
|
||||
stol ?: X [tes=gene bif=gene hob=gene] if [tes], then [bif], else [hob]
|
||||
tarn ^= X [fes=gene gav=gene vep=gene] [vep], since [gav] matches [fes]
|
@ -1,92 +0,0 @@
|
||||
Coinage of Watt
|
||||
|
||||
|
||||
Name Definition Example as printed Comment
|
||||
-------------------------------------------------------------------------------
|
||||
%blob opaque noun ~02Aw9cbfG21 ~0 prefix and uw
|
||||
%many coin list ._foo_bar__ underscore delimited
|
||||
~ %c codepoint ~--urbit.rocks~21 UTF-32 codepoint array
|
||||
~ %da absolute time ~2012.8.3..21.30.12..beef.dead galactic time, AD/BC
|
||||
~323-.6.10 death of Alexander
|
||||
~ %dr relative time ~d30.h20.m15.s12..dead.beef GT, relative
|
||||
~ %f flag .y boolean, 0=yes and 1=no
|
||||
.n
|
||||
~ %n null ~ absence of information
|
||||
~ %if IPv4 address .232.12.9.1
|
||||
~ %is IPv6 address .0.0.0.0.0.0.dead.beef
|
||||
~ %p phonemic ~pic memorable integer
|
||||
~-
|
||||
~nopgen
|
||||
~nopgen-durban
|
||||
~nopgen-durban--picmec-micmac
|
||||
~ %rd real-double .~28.4089
|
||||
~ %rh real-half .~~34
|
||||
~ %rq real-quad .~~~17
|
||||
~ %rs real-single .-576.78
|
||||
|
||||
~ %sb signed 2
|
||||
~ %sd signed 10
|
||||
~ %sh signed 16
|
||||
~ %sv signed 32
|
||||
~ %sw signed 64
|
||||
|
||||
~ %ub unsigned 2
|
||||
~ %ud unsigned 10
|
||||
~ %ux unsigned 16
|
||||
~ %uv unsigned 32
|
||||
~ %uw unsigned 64
|
||||
|
||||
~ %t text ~~urbit.rocks~21 literal UTF-8
|
||||
~ %ta legible span ~.under~-score [a/z 0/9 - . ~ _]
|
||||
~ %tas symbolic span foo-bar-baz [a/z -, start a/z]
|
||||
|
||||
sack prefix tree, with status (& good, | no good, * postponed)
|
||||
|
||||
a/z %tabc symbol &
|
||||
1/9 %ud unsigned decimal &
|
||||
0 %ud unsigned decimal &
|
||||
. %
|
||||
b %ub unsigned binary &
|
||||
h %uh unsigned hex &
|
||||
v %uv unsigned base32 &
|
||||
w %uw unsigned base64 &
|
||||
|
||||
.
|
||||
y,n %f booleans 0 and 1 &
|
||||
0/9 %rs single precision real *
|
||||
%ib ipv4 &
|
||||
%il ipv6 &
|
||||
~ %rh half precision real *
|
||||
. %rd double precision real *
|
||||
.. %rq quad precision real *
|
||||
-
|
||||
0/9 %rs single precision real *
|
||||
~ %rh half precision real *
|
||||
. %rd double precision real *
|
||||
.. %rq quad precision real *
|
||||
_ [%many] encoded list, _ and __
|
||||
|
||||
-
|
||||
1/9 %sd signed decimal
|
||||
0 %sd signed decimal
|
||||
b %sb signed binary
|
||||
h %sh signed hex
|
||||
v %sv signed base32
|
||||
w %sw signed base64
|
||||
-
|
||||
1/9 %sd signed decimal
|
||||
0 %sd signed decimal
|
||||
b %sb signed binary
|
||||
h %sh signed hex
|
||||
v %sv signed base32
|
||||
w %sw signed base64
|
||||
~ $c ucs32 string
|
||||
|
||||
~ %n null
|
||||
~ %t utf8 string (escaped)
|
||||
. %ta unescaped span
|
||||
- %p zero post
|
||||
a/z %p post
|
||||
0 [%blob] jam as base64
|
||||
* %da absolute date
|
||||
* %dr relative date
|
@ -1,58 +0,0 @@
|
||||
!! "hopven" %zike
|
||||
!# "hopdax" %zush
|
||||
!% "hopmit" %zoot
|
||||
!: "hopdig" %zalt
|
||||
!` "hoptic" %zole
|
||||
%* "mitras" %teck
|
||||
%+ "mitpod" %bung
|
||||
%- "mitnub" %fung
|
||||
%. "mitdot" %gnum
|
||||
%: "mitdig" %mung
|
||||
%= "mitben" %mack
|
||||
%^ "mithat" %tung
|
||||
%~ "mitsig" %hang
|
||||
%| "mitbar" %gath
|
||||
.* "dotras" %sail
|
||||
.= "dotben" %sing
|
||||
.? "dotask" %dust
|
||||
.^ "dothat" %vint
|
||||
:* "digras" %prex
|
||||
:+ "digpod" %trex
|
||||
:- "dignub" %twix
|
||||
:^ "dighat" %quax
|
||||
:~ "digsig" %slax
|
||||
;+ "lompod" %fist
|
||||
;- "lomnub" %mast
|
||||
=+ "benpod" %gant
|
||||
=- "bennub" %tang
|
||||
=< "bender" %claf
|
||||
=> "benred" %flac
|
||||
?! "askhop" %vern
|
||||
?& "askamp" %chan
|
||||
?* "askras" %moze
|
||||
?- "asknub" %grel
|
||||
?. "askdot" %lort
|
||||
?: "askdig" %trol
|
||||
?< "askder" %marg
|
||||
?= "askben" %plin
|
||||
?> "askred" %gram
|
||||
?| "askbar" %dorn
|
||||
?~ "asksig" %fent
|
||||
^$ "hatbuc" %germ
|
||||
^% "hatmit" %velt
|
||||
^* "hatras" %mave
|
||||
^+ "hatdig" %pock
|
||||
^- "hatnub" %cast
|
||||
^: "hatpod" %stil
|
||||
^= "hatben" %bran
|
||||
^? "hatask" %hint
|
||||
^@ "hatpat" %grit
|
||||
^~ "hatsig" %wost
|
||||
|% "barmit" %tash
|
||||
|* "barras" %pank
|
||||
|- "barnub" %vamp
|
||||
|: "bardig" %sunt
|
||||
|= "barben" %lome
|
||||
|? "barask" %rond
|
||||
|~ "barsig" %lonk
|
||||
|
@ -1,184 +0,0 @@
|
||||
new:
|
||||
bar |
|
||||
bas \
|
||||
buc $
|
||||
cab _ :: XX dun
|
||||
cen %
|
||||
col :
|
||||
com ,
|
||||
doq "
|
||||
dot .
|
||||
fas /
|
||||
gal <
|
||||
gar >
|
||||
hax #
|
||||
hep -
|
||||
kel {
|
||||
ker }
|
||||
ket ^
|
||||
lus +
|
||||
pel (
|
||||
pam &
|
||||
per )
|
||||
pat @ :: XX mat
|
||||
sel [
|
||||
sem ;
|
||||
ser ]
|
||||
sig ~
|
||||
soq '
|
||||
tar *
|
||||
tic `
|
||||
tis =
|
||||
wut ?
|
||||
zap !
|
||||
|
||||
old:
|
||||
del < gal
|
||||
pel ) par
|
||||
bar | bar
|
||||
hes $ hes
|
||||
sed } ker
|
||||
bot ' soc
|
||||
ket ^ ket
|
||||
sep - mus
|
||||
cab _ cab
|
||||
lep ( pal
|
||||
sig ~ sig
|
||||
com , com
|
||||
led > gar
|
||||
tam ; sem
|
||||
cas ? wut
|
||||
lyc = tis
|
||||
tar * tar
|
||||
dax # hax
|
||||
mit % cen
|
||||
tec ` tic
|
||||
deg : col
|
||||
ned ] ser
|
||||
tob " doc
|
||||
den [ sel
|
||||
nov \ bas
|
||||
von / fas
|
||||
des { kel
|
||||
pam & pam
|
||||
wat @ pat
|
||||
dot . dot
|
||||
pes + lus
|
||||
zap ! zap
|
||||
|
||||
Watt GTO: Gran Tabula Operazioni
|
||||
|
||||
|= %brts AM "bartis" "funk" declare a function
|
||||
|
||||
|
||||
|
||||
(Copied from watt.watt - recopy if in doubt.)
|
||||
::::
|
||||
::
|
||||
++ gene
|
||||
$& [p=gene q=gene]
|
||||
$%
|
||||
[%% p=axis]
|
||||
::
|
||||
[%brts p=gene q=gene]
|
||||
[%brls p=gene q=gene]
|
||||
[%brdt p=gene]
|
||||
[%brhp p=gene]
|
||||
[%brtr p=gene q=gene]
|
||||
::
|
||||
[%clkt p=gene q=gene r=gene s=gene]
|
||||
[%clhp p=gene q=gene]
|
||||
[%clls p=gene q=gene r=gene]
|
||||
[%clsg p=(list gene)]
|
||||
[%cltr p=(list gene)]
|
||||
::
|
||||
[%cntr p=twig q=gene r=(list >[p=gene q=gene])]
|
||||
[%cncl p=gene q=gene]
|
||||
[%cndt p=gene q=gene]
|
||||
[%cnkt p=gene q=gene r=gene s=gene]
|
||||
[%cnbc p=term]
|
||||
[%cnls p=gene q=gene r=gene]
|
||||
[%cnhp p=gene q=(list gene)]
|
||||
[%cnhx p=twig]
|
||||
[%cnsg p=twig q=gene r=gene]
|
||||
[%cnts p=twig q=(list >[p=gene q=gene])]
|
||||
::
|
||||
[%dtkt p=gene]
|
||||
[%dtls p=gene]
|
||||
[%dtpt p=term q=@]
|
||||
[%dtsg p=term q=*]
|
||||
[%dttr p=gene q=gene]
|
||||
[%dtts p=gene q=gene]
|
||||
[%dtwt p=gene]
|
||||
::
|
||||
[%bcbr p=gene q=gene]
|
||||
[%bccb p=gene]
|
||||
[%bccl p=(list gene)]
|
||||
[%bccn p=gene q=(list gene)]
|
||||
[%bcgr p=gene]
|
||||
[%bckt p=gene]
|
||||
[%bcpm p=gene q=gene]
|
||||
[%bctr p=gene]
|
||||
[%bcts p=?([%atom odor] %noun %cell %flag %null)]
|
||||
[%bcwt p=gene q=(list gene)]
|
||||
::
|
||||
[%ktbr p=gene]
|
||||
[%ktls p=gene q=gene]
|
||||
[%ktdt p=gene q=gene]
|
||||
[%ktgl p=gene q=gene]
|
||||
[%ktgr p=gene q=gene]
|
||||
[%kthp p=gene q=gene]
|
||||
[%ktpm p=gene]
|
||||
[%ktsg p=gene]
|
||||
[%ktts p=term q=gene]
|
||||
[%ktwt p=gene]
|
||||
::
|
||||
[%brcb p=gene q=(map term foot)]
|
||||
[%brcn p=(map term foot)]
|
||||
[%brkt p=gene q=(map term foot)]
|
||||
::
|
||||
[%sgbr p=gene q=gene]
|
||||
[%sgcl p=[p=@ q=@] q=gene]
|
||||
[%sgcn p=chop q=gene r=(list >[p=term q=gene]) s=gene]
|
||||
[%sgdt p=chop q=gene]
|
||||
[%sggl p=$|(term [p=term q=gene]) q=gene]
|
||||
[%sggr p=$|(term [p=term q=gene]) q=gene]
|
||||
[%sgbc p=term q=gene]
|
||||
[%sghx p=term q=gene]
|
||||
[%sgkt p=gene]
|
||||
[%sgls p=@ q=gene]
|
||||
[%sghp p=@ q=gene]
|
||||
[%sgpm p=gene q=gene]
|
||||
[%sgsg p=gene q=gene]
|
||||
[%sgts p=gene q=gene]
|
||||
::
|
||||
[%smcl p=gene q=(list gene)]
|
||||
[%smdq p=(list goop)]
|
||||
[%smsg p=gene q=(list gene)]
|
||||
::
|
||||
[%tsgl p=gene q=gene]
|
||||
[%tsgr p=gene q=gene]
|
||||
[%tsls p=gene q=gene]
|
||||
[%tshp p=gene q=gene]
|
||||
::
|
||||
[%wtbr p=(list gene)]
|
||||
[%wthp p=gene q=(list >[p=gene q=gene])]
|
||||
[%wtcl p=gene q=gene r=gene]
|
||||
[%wtcn p=gene q=gene]
|
||||
[%wtdt p=gene q=gene r=gene]
|
||||
[%wtts p=gene q=gene]
|
||||
[%wtgl p=gene q=gene]
|
||||
[%wtgr p=gene q=gene]
|
||||
[%wtls p=gene q=gene r=(list >[p=gene q=gene])]
|
||||
[%wtpm p=(list gene)]
|
||||
[%wtsg p=gene q=gene r=gene]
|
||||
[%wtzp p=gene]
|
||||
::
|
||||
[%zpcb p=spot q=gene]
|
||||
[%zpcm p=gene q=gene]
|
||||
[%zpcn ~]
|
||||
[%zpfs p=gene]
|
||||
[%zpsm p=gene q=gene]
|
||||
[%zpts p=gene]
|
||||
[%zpzp ~]
|
||||
==
|
@ -1,5 +0,0 @@
|
||||
s/csdt/wtdt/g
|
||||
s/cslc/wtts/g
|
||||
s/csps/wtls/g
|
||||
s/cssp/wtms/g
|
||||
s/mtdt/cndt/g
|
@ -1,117 +0,0 @@
|
||||
Dear Curtis,
|
||||
|
||||
We are sorry to inform you that your paper has not been selected for
|
||||
presentation at the Workshop on Self-sustaining Systems (S3) 2010 at The
|
||||
University of Tokyo, Japan.
|
||||
|
||||
Attached you will find the reviewers' comments. We hope you find them
|
||||
useful for improving your paper and for resubmitting to another event.
|
||||
|
||||
Best regards,
|
||||
Hidehiko and Robert
|
||||
|
||||
|
||||
=========================== REVIEWS ==========================
|
||||
|
||||
---------------------------- REVIEW 1 --------------------------
|
||||
PAPER: 3
|
||||
TITLE: Watt: A Self-Sustaining Functional Language
|
||||
|
||||
|
||||
SUMMARY
|
||||
|
||||
The author presents Watt and Nock. Watt is said to be a "functional
|
||||
programming for stupid people like you". Nock is said to be the "model".
|
||||
|
||||
EVALUATION
|
||||
|
||||
A workshop contribution does not have to be perfect, but it needs to
|
||||
be presented in a way that allows for a technical evaluation. This paper
|
||||
is written in a different style. The paper is unclear and incomprehensible.
|
||||
In many places it is vague, for example, "The relationship between Urth and
|
||||
Earth is unclear". At some places it is humurous, for example, "Urth code
|
||||
is nothing like Earth code". In other places it is simply insulting, for
|
||||
example, "Watt is functional programming for stupid people like you".
|
||||
|
||||
Comments:
|
||||
|
||||
"Watt is C, A is the machine instruction set, B is the C source for CC,
|
||||
and C is the executable for CC". C???
|
||||
"spec is is generally"=> "spec is generally"
|
||||
"Watt is dishwater; Urbit is vaporware". What?
|
||||
"eg" => "e.g.," Same for ie.
|
||||
"It has has all the flaws" => "It has all the flaws"
|
||||
"slowness, ..., and slowness". Once is slow enough.
|
||||
|
||||
|
||||
|
||||
---------------------------- REVIEW 2 --------------------------
|
||||
PAPER: 3
|
||||
TITLE: Watt: A Self-Sustaining Functional Language
|
||||
|
||||
|
||||
This paper presents a self-sustaining functional language: Watt.
|
||||
Watt is defined by a very small kernel which compiles itself to
|
||||
Nock, which is a simple non-lambda automaton. Nock is defined
|
||||
with very concise spec in English and pseudocode.
|
||||
|
||||
My major concern is that there is only an alpha release of the
|
||||
language with a lot of flaws and I can not tell from the paper
|
||||
if there are efficient and practical way to improve the flaws.
|
||||
For example, to have Watt program with reasonable performance.
|
||||
|
||||
Although it seems the author has very good reason to use an
|
||||
alien language to create Watt, it is still hard to understand
|
||||
the language and design intuition. It would be better if the
|
||||
paper can explain more about the design intuition, for example,
|
||||
why it only defines noun and does not introduce type, variables and
|
||||
functions etc. Also it is not clear about the programmability
|
||||
of Watt. What kinds of programs can easily be expressed? And what kinds
|
||||
of programs can it not easily express? And it still looks not clear
|
||||
to me in which domain Watt can beat other existing language and
|
||||
how.
|
||||
|
||||
Also it would be good if there is an example to illustrate Watt.
|
||||
A small but complete Watt program would be good.
|
||||
|
||||
There are some redundant words in the paper, for example: in
|
||||
Section 1.2 Practical, the 5th paragraph, the second sentence,
|
||||
there is an extra 'is' and in the 9th paragraph, the second
|
||||
sentence, there is an extra 'since'.
|
||||
|
||||
|
||||
|
||||
---------------------------- REVIEW 3 --------------------------
|
||||
PAPER: 3
|
||||
TITLE: Watt: A Self-Sustaining Functional Language
|
||||
|
||||
|
||||
The paper describes a minimal computation kernel (called Nock) and a statically typed functional language (Watt) built on top of it.
|
||||
|
||||
I think the approach of a minimal kernel with obvious execution semantics and a
|
||||
real language that maps on top of it is interesting and well worth pursuing. It
|
||||
is also clearly relevant to S3. The problem I have with this paper is that it
|
||||
does not really help me to understand what is going on with Nock and Watt.
|
||||
While Nock is probably fully specified by the formulas given, it is very hard
|
||||
to see what they are doing. It would have been helpful to see some detailed
|
||||
examples to understand Nock a bit more directly.
|
||||
|
||||
The same problem is even more pronounced for the section about Watt. We learn
|
||||
some things about Watt (compiles to Nock, statically typed, type inference,
|
||||
...), however, the actual working of the language remains unclear. The example
|
||||
code in Figure 2 and 3 is essentially inscrutable. I think that again some very
|
||||
simple and fully explained examples could have helped to understand the
|
||||
language and the intuition behind it better. On the other hand, I regard some
|
||||
parts of the Watt sections as not really necessary for the paper. It is not
|
||||
that helpful to get a list of semi-arbitrary names for glyphs and digraphs.
|
||||
|
||||
I understand the reasoning behind not using standard names for constructs in
|
||||
the language. However, the paper doesn't do a good enough job at telling me
|
||||
what the non-standard names actually mean, which leads to the fact that I don't
|
||||
understand the standard, nor the non-standard names.
|
||||
|
||||
Another issue of the paper is the idiosyncratic and partially arcane way to
|
||||
describe things and the unconventional language. I was left with the impression
|
||||
that this approach to writing was more an end in itself and not a mean to make
|
||||
the reader understand better. While reading the paper I sometimes felt vaguely
|
||||
insulted, which is not something you want your readers to be.
|
1029
Spec/watt/sss10.tex
1029
Spec/watt/sss10.tex
File diff suppressed because it is too large
Load Diff
@ -1,166 +0,0 @@
|
||||
@inproceedings{hindleymilner,
|
||||
author = {Damas, Luis and Milner, Robin},
|
||||
title = {Principal type-schemes for functional programs},
|
||||
booktitle = {POPL '82: Proceedings of the 9th ACM SIGPLAN-SIGACT symposium on Principles of programming languages},
|
||||
year = {1982},
|
||||
isbn = {0-89791-065-6},
|
||||
pages = {207--212},
|
||||
location = {Albuquerque, New Mexico},
|
||||
doi = {http://doi.acm.org/10.1145/582153.582176},
|
||||
publisher = {ACM},
|
||||
address = {New York, NY, USA},
|
||||
}
|
||||
|
||||
@misc{graham100yr,
|
||||
author = {Graham, Paul},
|
||||
year = {2003},
|
||||
title = {The hundred-year language},
|
||||
howpublished = "\url{http://www.paulgraham.com/hundred.html}"
|
||||
}
|
||||
|
||||
@article{skcombinator,
|
||||
author = {Sch{\"{o}}nfinkel, M.},
|
||||
citeulike-article-id = {2546429},
|
||||
journal = {Math Annalen},
|
||||
keywords = {lcbbbib},
|
||||
pages = {305--316},
|
||||
posted-at = {2008-03-17 14:30:48},
|
||||
priority = {3},
|
||||
title = {{\"{U}}ber die Bausteine der mathematischen Logik},
|
||||
volume = {92},
|
||||
year = {1924}
|
||||
}
|
||||
|
||||
@inproceedings{tromp,
|
||||
author = {John Tromp},
|
||||
title = {Binary Lambda Calculus and Combinatory Logic},
|
||||
booktitle = {Proceedings of the 5th Conference on Real Numbers and Computers, RNC5},
|
||||
year = {2003},
|
||||
pages = {214}
|
||||
}
|
||||
|
||||
@misc{grahamarc,
|
||||
author = {Graham, Paul},
|
||||
year = {2001},
|
||||
title = {Arc at 3 weeks},
|
||||
howpublished = "\url{http://www.paulgraham.com/arcll1.html}"
|
||||
}
|
||||
|
||||
@misc{grahamcore,
|
||||
author = {Graham, Paul},
|
||||
year = {2008},
|
||||
title = {First priority: core language},
|
||||
howpublished = "\url{http://www.paulgraham.com/core.html}"
|
||||
}
|
||||
|
||||
@misc{piumartacola,
|
||||
author = {Piumarta, Ian},
|
||||
year = {2005},
|
||||
title = {Making COLAs with Pepsi and Coke},
|
||||
howpublished = "\url{http://piumarta.com/papers/colas-whitepaper.pdf}"
|
||||
}
|
||||
|
||||
@misc{tarverqi,
|
||||
author = {Tarver, Mark},
|
||||
year = {2009},
|
||||
title = {Functional programming with Qi},
|
||||
howpublished = "\url{http://www.lambdassociates.org/Book/page000.htm}"
|
||||
}
|
||||
|
||||
@article{kaymaxwell,
|
||||
author = {Feldman, Stuart},
|
||||
title = {A Conversation with Alan Kay},
|
||||
journal = {Queue},
|
||||
volume = {2},
|
||||
number = {9},
|
||||
year = {2005},
|
||||
issn = {1542-7730},
|
||||
pages = {20--30},
|
||||
doi = {http://doi.acm.org/10.1145/1039511.1039523},
|
||||
publisher = {ACM},
|
||||
address = {New York, NY, USA},
|
||||
}
|
||||
|
||||
@article{mccarthylisp,
|
||||
author = {McCarthy, John},
|
||||
title = {History of LISP},
|
||||
journal = {SIGPLAN Not.},
|
||||
volume = {13},
|
||||
number = {8},
|
||||
year = {1978},
|
||||
issn = {0362-1340},
|
||||
pages = {217--223},
|
||||
doi = {http://doi.acm.org/10.1145/960118.808387},
|
||||
publisher = {ACM},
|
||||
address = {New York, NY, USA},
|
||||
}
|
||||
|
||||
@article{haskell98,
|
||||
author = {Simon {Peyton Jones} and others},
|
||||
title = {The {Haskell} 98 Language and Libraries: The
|
||||
Revised Report},
|
||||
journal = {Journal of Functional Programming},
|
||||
volume = 13,
|
||||
number = 1,
|
||||
pages = {0--255},
|
||||
month = {Jan},
|
||||
year = 2003,
|
||||
note =
|
||||
{\url{http://www.haskell.org/definition/}}
|
||||
}
|
||||
|
||||
@inproceedings{crashonly,
|
||||
author = {Candea, George and Fox, Armando},
|
||||
title = {Crash-only software},
|
||||
booktitle = {HOTOS'03: Proceedings of the 9th conference on
|
||||
Hot Topics in Operating Systems},
|
||||
year = {2003},
|
||||
pages = {12--12},
|
||||
location = {Lihue, Hawaii},
|
||||
publisher = {USENIX Association},
|
||||
address = {Berkeley, CA, USA},
|
||||
}
|
||||
|
||||
@inproceedings{unixtrouble,
|
||||
author = {Leon, Lorenzo De and Harris, William G. and Evens,
|
||||
Martha},
|
||||
title = {Is there really trouble with UNIX?},
|
||||
booktitle = {CHI '83: Proceedings of the SIGCHI conference
|
||||
on Human Factors in Computing Systems},
|
||||
year = {1983},
|
||||
isbn = {0-89791-121-0},
|
||||
pages = {125--129},
|
||||
location = {Boston, Massachusetts, United States},
|
||||
doi = {http://doi.acm.org/10.1145/800045.801595},
|
||||
publisher = {ACM},
|
||||
address = {New York, NY, USA},
|
||||
}
|
||||
|
||||
@article{kaysmalltalk,
|
||||
author = {Kay, Alan C.},
|
||||
title = {The early history of Smalltalk},
|
||||
journal = {SIGPLAN Not.},
|
||||
volume = {28},
|
||||
number = {3},
|
||||
year = {1993},
|
||||
issn = {0362-1340},
|
||||
pages = {69--95},
|
||||
doi = {http://doi.acm.org/10.1145/155360.155364},
|
||||
publisher = {ACM},
|
||||
address = {New York, NY, USA},
|
||||
}
|
||||
|
||||
@inproceedings{namedcontent,
|
||||
author = {Jacobson, Van and Smetters, Diana K. and Thornton,
|
||||
James D. and Plass, Michael F. and Briggs, Nicholas H. and
|
||||
Braynard, Rebecca L.},
|
||||
title = {Networking named content},
|
||||
booktitle = {CoNEXT '09},
|
||||
year = {2009},
|
||||
isbn = {978-1-60558-636-6},
|
||||
pages = {1--12},
|
||||
location = {Rome, Italy},
|
||||
doi = {http://doi.acm.org/10.1145/1658939.1658941},
|
||||
publisher = {ACM},
|
||||
address = {New York, NY, USA},
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
1. zeno: a 32-bit Nock interpreter
|
||||
|
||||
The core of the Watt reference distribution is zeno, a 32-bit
|
||||
Nock interpreter.
|
||||
|
||||
zeno is by no means as efficient as possible. For instance, a
|
||||
mature version would generate bytecode or machine code for
|
||||
bottleneck Nock, not rely entirely on hardcoded jets. However,
|
||||
zeno is designed to extend well in this sort of direction. It is
|
||||
also easily generalizable to a 64-bit model.
|
||||
|
||||
In many ways, zeno is a typical language runtime system.
|
||||
However, it has some unusual large-scale features:
|
||||
|
||||
- zeno is a direct interpreter.
|
||||
|
||||
zeno does not generate byte or machine code. It runs Nock
|
||||
formulas straight out of the noun tree. Nock itself can be
|
||||
seen as a kind of "treecode" - it is designed for direct
|
||||
execution.
|
||||
|
||||
- zeno cannot call foreign functions.
|
||||
|
||||
There is no way to call system-level C functions from Nock.
|
||||
How absurd would it be to make a system call from SQL? However
|
||||
absurd it would be, it would be much more absurd in Nock.
|
||||
|
||||
- zeno is jet-propelled.
|
||||
|
||||
Naive Nock is inherently inefficient; eg, decrement is an O(n)
|
||||
operation. To rectify this, zeno loads external accelerators
|
||||
called "jets," written in C, which match well-known Nock
|
||||
formulas (such as decrement). Every jet must match Nock
|
||||
semantics exactly; zeno can test this automatically.
|
||||
|
||||
Can a jet call a foreign function? Yes, but only if it is
|
||||
doing so to precisely match Nock semantics. For instance, if
|
||||
you have a tuned C implementation of a pure function such as
|
||||
crypto, Unicode collation, etc, etc, you can call it from a jet
|
||||
- so long as the jet formula matches perfectly.
|
||||
|
||||
It is impossible to perform input from a matching jet. If you
|
||||
perform output from jets, armed men will come to your house at
|
||||
night and kill you. You probably think I'm joking about this.
|
||||
|
||||
- zeno is pausable (and should be stable).
|
||||
|
||||
In other words, zeno can be halted and restarted without OS
|
||||
tools - it can participate in cooperative multitasking. More
|
||||
or less a requirement for any serious interpreter, but still
|
||||
worth mentioning. At present halting is a hack, but it should be
|
||||
upgraded to a genuine transaction model.
|
||||
|
||||
- zeno does not use the C stack for nested computations.
|
||||
|
||||
More or less implied in the previous.
|
||||
|
||||
- zeno does not use heuristic memory management.
|
||||
|
||||
Ie, garbage collection. May I speak frankly? Managing memory
|
||||
is the work of the programmer. GC is the work of Satan.
|
||||
When you assign the programmer's work to the interpreter,
|
||||
your fingers are working for Satan.
|
||||
|
||||
Nor is it a question of malloc() and "riding the bus." Since
|
||||
nouns are acyclic, the only question in noun memory management
|
||||
is how to improve on classic reference counting. Not a
|
||||
heuristic algorithm - and not one that requires pausing the UI.
|
||||
(If you're of a certain age, every time you see Safari spin its
|
||||
wheel, you think: "Garbage collecting... done.")
|
||||
|
||||
The default allocation scheme in zeno is not reference
|
||||
counting, but a three-pointer seniority scheme. This "Seuss
|
||||
system" works well for some algorithms, not so well for others.
|
||||
For the latter, there is reference counting.
|
363
Spec/zeno/2.txt
363
Spec/zeno/2.txt
@ -1,363 +0,0 @@
|
||||
Memory structure of Zeno (the loom):
|
||||
|
||||
1. Layout
|
||||
|
||||
You may know the typical Unix process layout, in which heap
|
||||
grows upward from low memory, and stack grows downward
|
||||
from high memory. The general advantage of this layout
|
||||
is zero fragmentation: heap and stack can grow as far
|
||||
toward each other as possible, until they meet.
|
||||
|
||||
This design is independent of the actual structures that grow
|
||||
from both ends of a linear address space. We might just as
|
||||
well have two heaps growing toward each other, or two stacks.
|
||||
|
||||
The loom allocator is the latter - two stacks, growing toward
|
||||
each other. A stack is actually a more powerful structure
|
||||
than a heap; if you have a stack and you want a heap, you can
|
||||
always push one on top of the stack. If you have a heap
|
||||
and you want a (contiguous) stack, you are SOL.
|
||||
|
||||
We call these opposed stacks "beams." A loom is two symmetric
|
||||
beams, one growing up from the bottom of memory, the other
|
||||
growing down from the top. The obvious constraint is that the
|
||||
beams must never cross. The unused area between them may be of
|
||||
any length and at any position.
|
||||
|
||||
(The loom design is not Nock-specific. Any functional language
|
||||
with acyclic data structures can use it. For instance, the same
|
||||
approach could be used for functional processing of XML.)
|
||||
|
||||
All loom addresses are word (32-bit) addresses. The loom
|
||||
pointer format can address 2^28 words, or 1GB, which is
|
||||
adequate for most light-duty programming tasks.
|
||||
|
||||
A "ray" is a 29-bit word offset onto a beam. If bit 29
|
||||
is 0, the ray grows forward from 0 and is "west." If bit
|
||||
29 is 1, the ray grows backward from the end of the loom,
|
||||
and is "east." East and west address spaces overlap; any
|
||||
word in the loom has an east address and a west address.
|
||||
|
||||
(The loom mapping cannot be implemented in hardware by any
|
||||
existing MMU I know of, but it is of course much simpler than a
|
||||
page table, and relatively alacritous in software. Twisted
|
||||
pointers of various kinds are typical ingredients in any dynamic
|
||||
language implementation.)
|
||||
|
||||
All u3 nouns are represented by the 32-bit word type "rat."
|
||||
If bit 31 of a rat is 0, it is a "cat" - a direct atom.
|
||||
Bits 0-30 of a cat are an unsigned 31-bit integer.
|
||||
|
||||
A rat can be a special value, u3_none - not a noun. A
|
||||
rat which is not u3_none is a "fox."
|
||||
|
||||
If bit 31 of a rat is 1, bits 0-28 are a ray which points
|
||||
to an indirect noun - a "dog." If bit 30 is 0, the dog is
|
||||
a "pom" - an indirect cell. If bit 30 is 1, the noun is a
|
||||
"pug" - an indirect atom.
|
||||
|
||||
Bit 29 on a dog is set iff the dog has special information
|
||||
behind the pointer, including but not limited to allocation
|
||||
header (block and reference count).
|
||||
|
||||
Although poms and pugs cannot be referenced through C pointers,
|
||||
because a ray is not a C pointer and requires specialized address
|
||||
arithmetic, their shape is described by these simple structures:
|
||||
|
||||
struct u3_cell {
|
||||
c3_w mug_w;
|
||||
u3_ray hed_ray;
|
||||
u3_ray tel_ray;
|
||||
};
|
||||
struct u3_atom {
|
||||
c3_w mug_w;
|
||||
c3_w len_w;
|
||||
c3_w buf_w[0];
|
||||
};
|
||||
|
||||
"c3_w" and "u3_ray" are both uint32_t. "buf_w" is really an
|
||||
array of length "len_w," of course.
|
||||
|
||||
"mug_w" is a 31-bit insecure hash which matches the Watt (mug)
|
||||
gate. The mug is computed lazily, as we need it - 0 is an
|
||||
out-of-band value. It is useful not just in associative and
|
||||
balanced trees, but also in simple functions like equality. If
|
||||
two dogs have the same mug, they may be different nouns; if they
|
||||
have different mugs, they must be different nouns.
|
||||
|
||||
We also borrow the mug field to maintain dag structure when
|
||||
copying nouns. Why copy a noun? See below.
|
||||
|
||||
2. Allocation (simple)
|
||||
|
||||
Now we consider the loom's allocation algorithm, the "Seuss
|
||||
system." The Seuss system manages memory with three pointers:
|
||||
"hat", "cap", and "mat".
|
||||
|
||||
"hat" and "cap", rays, are the tops of two opposing stack
|
||||
allocators, rays on opposite beams. Either the hat is east
|
||||
and the cap is west, or vice versa.
|
||||
|
||||
"mat" is a ray on the same beam as the cap, at or below it.
|
||||
|
||||
The loom is hence divided into three parts:
|
||||
|
||||
0 hat cap mat 1GB
|
||||
| | | | |
|
||||
|->.....box.......---pad----**can**........box.........<-|
|
||||
| |
|
||||
west east
|
||||
|
||||
The "pad" is free memory. The "box" is stable memory. The
|
||||
"can" is temporary memory. Again, the loom is symmetric. The
|
||||
can is always on one side of the pad, but that side can be east
|
||||
(as shown above), or west:
|
||||
|
||||
0 mat cap hat 1GB
|
||||
| | | | |
|
||||
|->.....box.......***can****--pad--........box.........<-|
|
||||
| |
|
||||
west east
|
||||
|
||||
The loom imposes a seniority restriction on all pointers. Box is
|
||||
senior to can. A reference stored in the can may point into the
|
||||
box, but a reference stored in the box cannot point into the can.
|
||||
|
||||
Since the box cannot point to the can, the can may be entirely
|
||||
destroyed without any corruption of the box. In general, the can
|
||||
is used for temporary allocation - ie, future garbage.
|
||||
|
||||
So the programmer in this context has two allocation choices.
|
||||
She can allocate garbage on the cap, or product on the hat.
|
||||
Rather than the normal model of stack and heap, she has two stack
|
||||
pointers - cap and hat - which grow toward each other.
|
||||
|
||||
In general, any function F is expected to produce its product as a
|
||||
fragmentation-free block of nouns, allocated in the box (on the
|
||||
hat), with pointers to older data in the box, but not of course
|
||||
the can. The can is expected to be reset after the call. Thus
|
||||
F is expected, as part of the calling convention, to pick up its
|
||||
own garbage.
|
||||
|
||||
Two stacks seems like a lot of choices for the programmer, every
|
||||
time she allocates a cell. Actually, though, this choice can in
|
||||
normal circumstances be made by the interpreter - because the
|
||||
only memory management problem in acyclic functional programming
|
||||
is composition.
|
||||
|
||||
When computing F(x), Zeno allocates product on the hat. When
|
||||
computing F(G(x)), Zeno allocates product of G(x) on the cap, and
|
||||
product of F(x) on the hat. If F(x) uses unmodified product of
|
||||
G(x), these must be copied from cap to hat. For the product of
|
||||
G(x) is, by definition, garbage.
|
||||
|
||||
And when computing F(G(H(x)))? Sadly, the calculations that
|
||||
produce garbage have a nasty tendency to allocate temporary nouns
|
||||
of their own. If we had an infinite number of beams, we'd be
|
||||
set. But we only have two.
|
||||
|
||||
Zeno handles the H(x) problem by reversing the beams. Let's work
|
||||
through a simple example:
|
||||
|
||||
As we start:
|
||||
|
||||
0 hat cap mat 1GB
|
||||
| | | | |
|
||||
|->...............----------*******....................<-|
|
||||
| |
|
||||
west east
|
||||
|
||||
"." is permanent memory, the box; "*" is temporary memory, the
|
||||
can; "-" is free memory, the pad.
|
||||
|
||||
Our goal is to allocate the product of F as ".", by pushing
|
||||
the hat a couple of columns east.
|
||||
|
||||
First, we depart to compute G. The product of G is "!", and is
|
||||
allocated on the cap. Not that G is hardcoded for this role, of
|
||||
course - it allocates its product on the hat, like any function.
|
||||
|
||||
old old old
|
||||
0 hat cap mat 1GB
|
||||
| | | | |
|
||||
|->...............--------!!*******....................<-|
|
||||
| | | |
|
||||
west mat hat east
|
||||
cap
|
||||
now now
|
||||
|
||||
Thus, so long as the VM exhibits east/west independence,
|
||||
we can compute G with the same code that computed F, on the
|
||||
opposite beam. And if G depends on another function, H?
|
||||
|
||||
old old old
|
||||
0 hat cap mat 1GB
|
||||
| | | | |
|
||||
|->...............????----!!*******....................<-|
|
||||
| | | | |
|
||||
west mat cap hat east
|
||||
now now now
|
||||
|
||||
The garbage of G - which is the product of H - is "?". How do we
|
||||
get H to allocate on the cap? By reversing the beams again.
|
||||
This solution can be iterated indefinitely - until, of course,
|
||||
the cap and hat collide. At this point you are out of memory.
|
||||
|
||||
We complete the calculation of G by retreating:
|
||||
|
||||
old old old
|
||||
0 hat cap mat 1GB
|
||||
| | | | |
|
||||
|->...............--------!!*******....................<-|
|
||||
| | | | |
|
||||
| hat cap mat east
|
||||
west now now now
|
||||
|
||||
We have now computed G and placed its product on the cap. So we
|
||||
are ready to compute F, placing its product on the hat. The
|
||||
product of F is "~":
|
||||
|
||||
old old old
|
||||
0 hat cap mat 1GB
|
||||
| | | | |
|
||||
|->...............~~------!!*******....................<-|
|
||||
| | | | |
|
||||
west hat cap mat east
|
||||
now now now
|
||||
|
||||
We then retract the cap, freeing the product of G:
|
||||
|
||||
old old old
|
||||
0 hat cap mat 1GB
|
||||
| | | | |
|
||||
|->...............~~--------*******....................<-|
|
||||
| | | | |
|
||||
west hat cap mat east
|
||||
now now now
|
||||
|
||||
Thus we have pushed the product of F, without any garbage,
|
||||
on the hat. The algorithm is optimal, except for the cost of
|
||||
copying temporary results - which varies from 0 to prohibitive.
|
||||
|
||||
One case in which the algorithm above needs improvement is
|
||||
tail-call compaction. In a tail-call loop, F(x) resolves to
|
||||
F(G(x)) - which resolves to F(G(G(x))), etc, etc.
|
||||
|
||||
The loom knows the final destination at all times and never needs
|
||||
to copy the result - but it also accumulates a pile of
|
||||
intermediate results, G(x), G(G(x)), etc, on the cap. Where
|
||||
"!" is G(x), "?" is G(G(x)), '#" is G(G(G(x))):
|
||||
|
||||
old old old
|
||||
0 hat cap mat 1GB
|
||||
| | | | |
|
||||
|->...............----##??!!*******....................<-|
|
||||
| | | | |
|
||||
| hat cap mat east
|
||||
west now now now
|
||||
|
||||
This is obviously craptastic. Can anything be done about it?
|
||||
That depends on one thing - whether G(G(x)) references G(x). If
|
||||
it does (for example, when inverting a list), no compaction is
|
||||
possible - there is nothing to free.
|
||||
|
||||
But if G(G(x)) does not reference G(x), we can simply slide
|
||||
G(G(x)) down the cap over its parent. In general, tail calls can
|
||||
be computed in fixed space if the new tail context does not
|
||||
reference the old tail context.
|
||||
|
||||
How can we determine this? Well, we could analyze the formula.
|
||||
In a compilation context, this is probably what we'd do. In an
|
||||
interpreter, however, there is no room for static analysis.
|
||||
|
||||
Therefore, the only way to determine whether G(G(x)) references
|
||||
G(x) is a brute-force nano-garbage collection. We simply chase
|
||||
any pointers in G(G(x)) and see if they fall into G(x). If not,
|
||||
we slide "?" down over "!". This is called "tamping."
|
||||
|
||||
Tamping is a effective optimization because its cost is
|
||||
proportional to the size of G(G(x)). This may be an arbitrarily
|
||||
large and complex noun, but we do not have to chase pointers
|
||||
outside G(G(x)). "?" cannot address "!" through either "." or
|
||||
"*", because "." and "*" cannot point to "!" due to seniority.
|
||||
|
||||
A tamped loom makes an excellent top-level default. For many
|
||||
algorithms, it is entirely impractical. But, if the implementor
|
||||
of the algorithm knows this, it is easy to request special
|
||||
treatment. The pad is an unfragmented block of memory - the
|
||||
appropriate input for any memory-management algorithm.
|
||||
|
||||
The Watt programmer, optimizing for Zeno, does so by using hints
|
||||
(Nock 11) to select one of three memory disciplines: %flee,
|
||||
%stay, and %keep. The above is the %flee discipline, which
|
||||
is the default. In general, the memory discipline defines what
|
||||
Zeno does where, under %flee, it would depart.
|
||||
|
||||
The %stay discipline never departs. Instead, it uses the cap as
|
||||
a conventional stack, and the hat as a conventional heap. Only
|
||||
control information is allocated on the cap. All product nouns -
|
||||
F(x), G(x) and H(x) - are allocated on the hat:
|
||||
|
||||
old old old
|
||||
0 hat cap mat 1GB
|
||||
| | | | |
|
||||
|->...............??!!~~----*******....................<-|
|
||||
| | | | |
|
||||
west hat cap mat east
|
||||
now now now
|
||||
|
||||
What do we do with G(x) and H(x)? We leak the motherfsckers.
|
||||
But at least there is no copying.
|
||||
|
||||
This is remedied by the %keep discipline. Remember that
|
||||
disciplines affect one thing: departure policy. In the %flee
|
||||
discipline, we depart; in the %stay discipline, we remain.
|
||||
|
||||
In %keep, we depart - but install a refcount allocator. In the
|
||||
departing frame, this upgrades the hat to a reference-counted
|
||||
heap. It also sets the memory discipline to %stay. Thus, in the
|
||||
departing frame, we do not leak but rather free.
|
||||
|
||||
Since we are refcounting in the departed frame, not the current
|
||||
frame, the result still needs to be copied back if used. It is
|
||||
good to copy out of an allocator, because the allocator doubles
|
||||
the size of a cell.
|
||||
|
||||
Therefore, if we compute F under %keep, we observe, in the
|
||||
departed context,
|
||||
|
||||
old old old
|
||||
0 hat cap mat 1GB
|
||||
| | | | |
|
||||
|->...............-----?=!?&*******....................<-|
|
||||
| | | | |
|
||||
west mat hat rut east
|
||||
cap
|
||||
now now now
|
||||
|
||||
"&" is the free list; "=" is a free block.
|
||||
|
||||
Using the allocator adds a fourth memory pointer, "rut", to the
|
||||
Seuss system. The rut sets (a) the location of the free list
|
||||
and (b) the boundaries of reference control. If the rut is set,
|
||||
before advancing the hat, zeno will try to allocate out of the
|
||||
free list.
|
||||
|
||||
Since nouns are acyclic, reference counting is always effective.
|
||||
Why bother with this loom business, then? Why not just a simple
|
||||
reference-counting allocator for all of memory?
|
||||
|
||||
One of the big performance problems with refcounting is its
|
||||
tendency to absolutely screw the hell out of your cache. Address
|
||||
space can be backed by anything - from on-chip cache to a tape
|
||||
drive. Many programs pass around references to memory they use
|
||||
much more rarely, and when they use this memory it is only to
|
||||
read them. If the entire space is in one reference pool, this
|
||||
will generate a shower of *writes* to deep data structures that
|
||||
are essentially static.
|
||||
|
||||
Reference counting in the loom is *local*. References only need
|
||||
to be tracked for nouns between rut and hat. Even if these nouns
|
||||
reference reference-counted nouns elsewhere in the loom, counts
|
||||
do not need to be updated for any noun between 0 and hat, or rut
|
||||
and 1GB.
|
@ -1,252 +0,0 @@
|
||||
1: Introduction and philosophy
|
||||
==============================
|
||||
|
||||
Urbit is a new programming and execution environment
|
||||
designed from scratch. Any resemblance to existing languages
|
||||
or operating systems is coincidental, cosmetic, or inevitable.
|
||||
|
||||
## Urbit is a stack ##
|
||||
|
||||
Nock is a stateless virtual machine defined in 200 words. The
|
||||
Nock machine is sealed - all execution is "pure." Nock's goal is
|
||||
extreme commoditization of computing semantics.
|
||||
|
||||
Hoon is a high-level language which defines itself in Nock. Its
|
||||
self-compiling kernel, 7000 lines of code, specifies Hoon
|
||||
unambiguously; there is no Hoon spec. Hoon can be classified as
|
||||
a pure, strict higher-order static type-inferred functional
|
||||
language, with co/contra/bivariance and genericity. However,
|
||||
Hoon does not use lambda calculus, unification, or other
|
||||
constructs from "PL theory." Hoon also excels at handling and
|
||||
validating untyped data, a common task on teh Internets. Its
|
||||
syntax is entirely novel and initially quite frightening.
|
||||
|
||||
Arvo is a deterministic functional operating system defined in
|
||||
Hoon. While still basically a toy, it can serve web apps and
|
||||
network securely with other Arvo instances. An Arvo instance
|
||||
is designed to be a simple independent computer in the cloud.
|
||||
|
||||
It's possible to use Nock without Hoon, but there is no obvious
|
||||
reason to do so. It's not necessary to learn Nock to learn Hoon,
|
||||
but it helps a lot. The same for Hoon and Arvo. Therefore,
|
||||
we'll work through all three in order. in order. If you're
|
||||
convinced that you want to learn Urbit, feel free to skip the
|
||||
justifications below.
|
||||
|
||||
## Nock ##
|
||||
|
||||
Nock is a virtual machine, like the Java VM or Microsoft CLR.
|
||||
Just as you execute Java by compiling it to the JVM, you execute
|
||||
Hoon by compiling it to Nock.
|
||||
|
||||
Do you need to learn Nock to learn Hoon? In theory, no, just as
|
||||
you don't need to know anything about the JVM to learn Java.
|
||||
Indeed, in theory we could compile Hoon to the JVM (or Java to
|
||||
Nock). What's the difference between Nock and the JVM, anyway?
|
||||
Why do we need a new virtual machine? And since we'll only be
|
||||
programming in Hoon, why do we care how Hoon is executed?
|
||||
|
||||
There is no formal difference between Nock and the JVM. In
|
||||
theory, both are Turing-complete computers. In practice, they
|
||||
have just about nothing in common.
|
||||
|
||||
The main practical difference is that Nock is two orders of
|
||||
magnitude simpler. The JVM spec is a book. The Nock spec is 200
|
||||
words; it fits on a T-shirt; it gzips to 371 bytes. There are
|
||||
lots of other differences, but all follow from this.
|
||||
|
||||
Why does size matter? The goal of any programming language is to
|
||||
become a standard. Universal standardization equals world
|
||||
domination. The simpler a specification is, the easier to
|
||||
standardize. Nock is a practical interpreter as simple as the
|
||||
most frozen and fundamental of protocols or formats: IPv4, XML,
|
||||
JSON. A 200-word spec needs a standards process like a fish
|
||||
needs swim lessons. When a T-shirt goes to war with a book, it's
|
||||
like tanks against cavalry - or should be, anyway.
|
||||
|
||||
Since every program in Hoon (including the Hoon compiler) reduces
|
||||
to Nock, every program in Hoon inherits Nock's precision. If two
|
||||
Nock interpreters produce different results, one is wrong, and it
|
||||
is always easy to tell which - without a "standards lawyer." Can
|
||||
we imagine the end of incompatiblity?
|
||||
|
||||
Essential to actually realizing this promise is a second
|
||||
difference between Nock and the JVM, which is that the JVM can
|
||||
call Unix system calls and libraries, and Nock can't. Nock has
|
||||
no "native methods" or foreign-function interface. Nor is it
|
||||
ever extended, embellished, forked, improved or advanced.
|
||||
|
||||
Java began as a portable language; so did C. Most Java today is
|
||||
server-side Java, dependent no less than C on library and OS
|
||||
configuration. It is possible to write portable Java; it is
|
||||
possible to write portable C. It is not possible to write
|
||||
unportable Nock or Hoon. It is also not possible to write
|
||||
insecure Nock or Hoon, unless your interpreter is so broken
|
||||
there's an actual hole in its skull.
|
||||
|
||||
How does Nock obtain native CPU performance, if it can't call
|
||||
native code? This is actually a much more pressing problem in
|
||||
Nock than in conventional virtual machines, like the JVM, because
|
||||
naive Nock is hopelessly inefficient. Nock is defined in a page
|
||||
of pseudocode, and a naive interpreter can be written in a page
|
||||
of any language. But since Nock's only arithmetic operator is
|
||||
increment, decrement in your one-page interpreter is an `O(n)`
|
||||
operation. Addition is `O(n^2)`. And so on.
|
||||
|
||||
The programmer cannot solve this problem by calling a C function,
|
||||
because Nock can't do that. In theory, an optimizing Nock
|
||||
interpreter might be able to analyze the code and reduce it to a
|
||||
simpler equivalent. But this would be a true research problem.
|
||||
|
||||
Instead, a practical Nock engine simply recognizes code it knows
|
||||
and substitutes equivalent C functions, or "jets." For instance,
|
||||
in theory there are many different ways to express decrement in
|
||||
Nock, but in practice the Nock interpreter will execute only one:
|
||||
the decrement function in the Hoon kernel. Therefore, the only
|
||||
Nock decrement that must be optimized is the code that Hoon
|
||||
generates when it compiles its own decrement. All others will
|
||||
suck, so don't roll your own. Code recognition, as opposed to
|
||||
code analysis, is not a research problem.
|
||||
|
||||
Jet propulsion separates mechanism and policy, transferring the
|
||||
task of achieving native performance from the programmer to the
|
||||
sysadmin. The Hoon programmer must still use hints to mark
|
||||
functions for jet recognition, but cannot control or discern how
|
||||
these functions are actually executed. Of course, a correct jet
|
||||
is indistiguishable in every way, except timing, from naive Nock.
|
||||
The 200-word spec defines the result, not the algorithm.
|
||||
|
||||
We can see a jet as a sort of "functional device driver." For
|
||||
instance, an OpenGL programmer today has no idea whether her GL
|
||||
operations are implemented in software or by a GPU. This
|
||||
abstraction is essential to modern graphics programming.
|
||||
|
||||
When we compare jets to native calls, what are the pros and cons?
|
||||
Jets have only one disadvantage: high-performance code must be
|
||||
written twice, once in Hoon and once in C. Indeed, from the C
|
||||
programmer's perspective, Hoon is a specification language for
|
||||
your C functions. Hoon specifications are executable, of course,
|
||||
so you can test the two implementations against each other -
|
||||
again, transparently to the programmer. Moreover, the jet can
|
||||
even fail in special cases and drop back to the interpreter.
|
||||
|
||||
Hoon ships with a Nock that jet-propels most of the Hoon kernel,
|
||||
including most of the Hoon compiler. If you're wondering how we
|
||||
wrote Hoon in Hoon when we didn't have Hoon, the answer is that
|
||||
we wrote Hoon in C and evolved this C code into a mere jet. This
|
||||
process is not recommended unless absolutely necessary - by far
|
||||
the best way to write the jet pair is to write the Hoon first.
|
||||
|
||||
## Hoon ##
|
||||
|
||||
If I can summarize Hoon's goal, it's to be the C of functional
|
||||
programming. If you're not so arthritic that you learned to code
|
||||
in Turbo Pascal, you may never fully appreciate the metaphor.
|
||||
|
||||
All languages in the Algol procedural family, including both C
|
||||
and Pascal, map straightforwardly onto a conventional CPU. But
|
||||
Pascal and C handle this mapping very differently. Pascal and C
|
||||
both have pointers and arrays, but Pascal works hard to treat
|
||||
both pointers and arrays as mathematical abstractions. C drops
|
||||
the abstraction; it makes no bones about the fact that a pointer
|
||||
is a memory address.
|
||||
|
||||
To a Pascal purist, to anyone who thinks mathematically, this
|
||||
seemed hideous. C isn't really a high-level language at all -
|
||||
it's a glorified macro assembler. Mankind retreats to the cave.
|
||||
But to programmers who are not natural mathematicians, whose
|
||||
minds are mechanical rather than abstract, C is a lifesaver.
|
||||
Since most mathematicians are also good mechanical thinkers,
|
||||
whereas very few people are naturally adept at abstraction,
|
||||
C slew and pillaged the once promising empire of Pascal.
|
||||
|
||||
There are two broad families of functional language available
|
||||
today: Haskell/ML, and Lisp. The Haskell family is relatively
|
||||
abstract; the Lisp family, relatively concrete. Perhaps Lisp is
|
||||
about as abstract as Pascal; Haskell is far more abstract. Both
|
||||
rest on the fundamental abstraction of functional programming,
|
||||
the lambda calculus, and more generally the foundational
|
||||
metamathematics of the late 19th and early 20th centuries.
|
||||
|
||||
Hoon has nothing to do with any of this stuff. It has functions
|
||||
and types, or what appear to be functions and types. On closer
|
||||
inspection, they are not abstractions at all, just glorified Nock
|
||||
macros.
|
||||
|
||||
If we compare these concrete patterns to the genuine abstractions
|
||||
of Haskell, we see that - as with Pascal and C - Hoon is roughly
|
||||
as expressive as Haskell. Haskell has higher-order type
|
||||
inference; Hoon has "higher-order" "type" "inference." Some
|
||||
Haskell extensions have dependent types - Hoon has "refined"
|
||||
"types." Hoon, like Lisp, unlike Haskell, is also very
|
||||
comfortable with typeless data; it should be, because it has no
|
||||
types, only "types." The Hoon features and the Haskell
|
||||
abstractions have nothing in common - except that they solve the
|
||||
same problems for you, the programmer. In short, Hoon next to
|
||||
Haskell is a white shark next to a killer whale. The apparent
|
||||
resemblance is strictly superficial.
|
||||
|
||||
So we could describe Hoon as a pure, strict, higher-order typed
|
||||
functional language. But don't do this in front of a Haskell
|
||||
purist, unless you put quotes around "typed," "functional," and
|
||||
possibly even "language." We could also say "object-oriented,"
|
||||
with the same scare quotes for the cult of Eiffel.
|
||||
|
||||
Knowing Pascal made it harder, not easier, to learn C. Knowing
|
||||
Haskell or Lisp makes it harder to learn Hoon. Indeed, knowing
|
||||
either would have made it impossible for me to write Hoon. I do
|
||||
know C, of course, and the spirit of K&R is all over Hoon. Or so
|
||||
I'd like to think. Just as C is little more than a macro
|
||||
assembler for machine code, Hoon is little more than a macro
|
||||
assembler for Nock.
|
||||
|
||||
The most basic difference between Hoon and other languages is
|
||||
that Hoon is defined in Hoon. There is no formal Hoon spec -
|
||||
just a self-compiling compiler written in Hoon. The target of
|
||||
this compiler is, of course, Nock. Thus Hoon is as precisely
|
||||
defined as Nock, which is quite precisely indeed.
|
||||
|
||||
This would be true regardless of the size of Hoon in Hoon, but
|
||||
Hoon in Hoon is in fact quite small. The Hoon kernel is 7000
|
||||
lines; it gzips to 25K. But this includes not only the
|
||||
self-compiling compiler, but also all the standard libraries it
|
||||
needs. The compiler alone is 2500 lines, including a very
|
||||
intricate "monadic" parser, a non-Hindley-Milner "type inference"
|
||||
engine, and a Nock code generator. This reflects both the
|
||||
internal simplicity of Hoon and its expressiveness. If you know
|
||||
these 2500 lines, and an expert should, you _know_ Hoon.
|
||||
|
||||
On the other hand, the _apparent_ complexity of Hoon is very
|
||||
high. When you open a Hoon file, you are confronted with an
|
||||
enormous avalanche of barely structured line noise. Again this
|
||||
reminds us of C, which makes no attempt at the kind of abstract
|
||||
prettiness we expect from a Pascal or a Haskell. Learning Hoon
|
||||
involves learning nearly 100 ASCII digraph "runes."
|
||||
|
||||
Is this a harsh learning curve? Of course it is. On the other
|
||||
hand, it is not a mathematical task, but a mechanical one. It is
|
||||
trivial compared to the task of learning the Chinese alphabet,
|
||||
memorizing the Qu'ran, etc, all rote mental tasks routinely
|
||||
performed by normal human 11-year-olds. If you have an
|
||||
11-year-old who understands the Hindley-Milner algorithm, you
|
||||
have a remarkable young mathematician.
|
||||
|
||||
A practical programming language is first and foremost a UI for
|
||||
programmers - meaning human programmers. Concrete languages beat
|
||||
abstract ones because they play to the strengths of the human
|
||||
brain, and avoid its weaknesses. Functional programming is
|
||||
traditionally reserved for the topmost echelon of natural talent.
|
||||
I'd like to think that anyone who can learn to fix a Chevy can
|
||||
learn to write a Hoon function. We'll see if that's true.
|
||||
|
||||
A programming language is called a language for a reason - it
|
||||
should activate the human linguistic lobes. Learning Hoon
|
||||
is like learning a language very alien to your first, such as
|
||||
Chinese from English. Before you know Hoon, it looks like
|
||||
squiggles. Once you know Hoon, and the rote task of syntax
|
||||
processing is hardwired, you look at your screen and _see_
|
||||
the function. Or, at least, I do - I hope you can too.
|
||||
|
||||
## Arvo ##
|
||||
|
||||
Lorem ipsum.
|
File diff suppressed because it is too large
Load Diff
@ -1,81 +0,0 @@
|
||||
2: Hoon - Philosophy
|
||||
=========
|
||||
|
||||
Yo, let's write some Hoon!
|
||||
|
||||
In this tutorial we're going to try to learn Hoon, without first
|
||||
learning either Nock or Arvo. You probably should at least skim
|
||||
the Nock doc though - learning Hoon without understanding Nock is
|
||||
a little like learning C without understanding how a CPU works.
|
||||
It is not easier than knowing both, it's harder.
|
||||
|
||||
As for Arvo, all we'll use of it here is the power to edit a
|
||||
library and type expressions on the command line. These are
|
||||
not hard things and we'll explain them as we go along.
|
||||
|
||||
We'll cover Hoon in three sections - philosophy, syntax, and
|
||||
semantics. You should probably just skip to syntax.
|
||||
|
||||
The good news about Hoon is that it compiles itself to Nock in
|
||||
3400 lines of Hoon. If this number is accurate (it is), Hoon
|
||||
is very expressive, or very simple, or both. (It's both.) The
|
||||
bad news is that it really has nothing at all in common, either
|
||||
syntactically or semantically, with anything you've used before.
|
||||
|
||||
In particular, please remember that _you are not too stupid_ to
|
||||
program in Hoon. Hoon is FP made stupid - for the stupid, by the
|
||||
stupid. (This is the real reason it's only 3400 lines.)
|
||||
|
||||
Maybe you've had some bad experiences with a higher-order typed
|
||||
functional language - like Haskell or OCaml. Dare we suspect
|
||||
that these experiences came on one of our nation's fine college
|
||||
campuses? Well, we have to admit, Hoon is a higher-order typed
|
||||
functional language. But really, don't let this discourage you.
|
||||
|
||||
First, Hoon hasn't been to college. Second, at least it's
|
||||
strict. And third, we don't use any of that PL theory shit.
|
||||
We're OS guys. Our mission is not to praise PL, but to drive it
|
||||
out of the CS department with a flaming sword, back to the
|
||||
fundless ghetto of math where it belongs. Hoon is 'street FP.'
|
||||
The only functional language it gives any real props to is JS.
|
||||
JS sucks, of course, but at least it's a player. (Scala is a
|
||||
player too, but it's also the size of a blue whale's ass.)
|
||||
|
||||
We hope we're not being too harsh on PL theory. The fact is that
|
||||
this branch of mathematics (constructive logic or whatever) is a
|
||||
perfectly adequate and remarkably elegant formal description of
|
||||
computing. But, of course, it is not the only such description.
|
||||
And it was originally created for mathematicians, not coders.
|
||||
|
||||
Nor is the math department's standard of elegance the only such
|
||||
standard. The programmer has his own design sense, which
|
||||
corresponds more closely to usability in a UI sense. To read a
|
||||
program is to simulate it in your head, and the skill needed to
|
||||
do this revolves around tracing long complex sequences of very
|
||||
simple transformations.
|
||||
|
||||
For these simple transformations, I think, we use a completely
|
||||
different processing path - less a mathematical intution, than a
|
||||
mechanical one. This is why many good C programmers are bad at
|
||||
symbolic math. Of course, we all should be good at all things -
|
||||
but are we? And this is why Hoon is not Haskell.
|
||||
|
||||
For example, it's very true and cool that you can represent all
|
||||
data as functions. It's also pretty neat that you can take a
|
||||
leak while standing on your head, and you always hit the bowl.
|
||||
To an OS guy it seems more interesting to represent functions as
|
||||
data. Like, you know how to send data over the network. Do you
|
||||
know how to send functions over the network?
|
||||
|
||||
(On the other hand, if only because Hoon is very immature, Haskell
|
||||
is good at many things that Hoon isn't good at. For instance,
|
||||
good list/container comprehensions are the pons asinorum of a
|
||||
functional language. Hoon's actually kind of suck. This is not
|
||||
(well, mostly not) because Hoon sucks, but because (a) they are
|
||||
some of the oldest library code in the system, and (b) they have
|
||||
never seen either end of a top-notch functional programmer.
|
||||
Despite the fact that category theory can go bite itself, people
|
||||
with more FP experience on more mature languages probably have a
|
||||
lot to contribute here.)
|
||||
|
||||
Anyway. That's enough philosophy. Come on, who needs it?
|
@ -1,326 +0,0 @@
|
||||
3: Hoon - Syntax
|
||||
=========
|
||||
|
||||
Now, let's actually look at Hoon. Really, try not to recoil
|
||||
in horror. It's actually not anything like line noise.
|
||||
|
||||
Open the Hoon kernel - `urb/les/arvo/hoon.hoon`. Let's look at
|
||||
the full, official decrement function (line 549):
|
||||
|
||||
++ dec
|
||||
~/ %dec
|
||||
|= a=@
|
||||
^- @
|
||||
?< =(0 a)
|
||||
=+ b=@
|
||||
|-
|
||||
?: =(a +(b))
|
||||
b
|
||||
$(b +(b))
|
||||
|
||||
Whaa?
|
||||
|
||||
Any attempt to understand this in terms of any language you
|
||||
already know would clearly be mistaken. In both syntax and
|
||||
semantics, learning to program in Hoon is learning to program all
|
||||
over again. When we say from scratch, we mean from scratch!
|
||||
|
||||
It's actually worse than that - learning Hoon is learning to
|
||||
_read_ all over again. Hoon is a keyword-free language - any
|
||||
alphanumeric text in the program is part of the program. Where
|
||||
other languages have reserved words, Hoon has squiggles.
|
||||
|
||||
We use so many of these ASCII glyphs that we like to be able
|
||||
to read them out loud. A language is meant to be _said_. The
|
||||
squiggles have conventional names, sort of, some of them, some of
|
||||
them easy to say, others not so much. So we've renamed them:
|
||||
|
||||
ace space gal < per )
|
||||
bar | gar > sel [
|
||||
bas \ hax # sem ;
|
||||
buc $ hep - ser ]
|
||||
cab _ kel { sig ~
|
||||
cen % ker } soq '
|
||||
col : ket ^ tar *
|
||||
com , lus + tec `
|
||||
doq " pam & tis =
|
||||
dot . pat @ wut ?
|
||||
fas / pel ( zap !
|
||||
|
||||
You just have to memorize these names. Sorry.
|
||||
|
||||
But is this at least enough symbols? Alas, nowhere near.
|
||||
ASCII's glyph supply is not the greatest, but we can make all the
|
||||
squiggles we need by forming digraphs, or _runes_.
|
||||
|
||||
To pronounce a rune, concatenate the glyph names, stressing the
|
||||
first syllable and softening the second vowel into a "schwa."
|
||||
Hence, to say `~.`, say "sigdot." To say `|=`, say "bartis."
|
||||
Which has an inevitable tendency to turn into "barts" - a sin
|
||||
to be encouraged. In any language actually spoken by actual
|
||||
humans, laziness soon rounds off any rough edges.
|
||||
|
||||
So if we had to read the above decrement, omitting the spaces
|
||||
(which only a real purist would pronounce), we'd say: "luslus dec
|
||||
sigfas cen dec bartis a tis pat sigbar soq dec soq ketcab pat wutgal
|
||||
tis pel zero a per tislus b tis pat barhep wutcol tis pel a lus pel
|
||||
b per per b buc pel b lus pel b per per." The authorities would
|
||||
then arrive, and drag us out in a big net. Definitely don't do
|
||||
this at the airport.
|
||||
|
||||
Geeks being solitary by nature, opportunities for reading code
|
||||
aloud are limited. But studies by actual scientists have shown
|
||||
that even when we read silently, we activate the motor cortex
|
||||
that controls our vocal cords. Even if we never speak these
|
||||
squiggles, they're easier to _think_ if bound to simple sounds.
|
||||
|
||||
(And don't worry if you can't get yourself to say "lus" instead
|
||||
of "plus" for `+`, or "tar" instead of "star" for `*` - I have
|
||||
this problem myself. It's much easier to replace "underscore"
|
||||
with "cab" or "ampersand" with "pam.")
|
||||
|
||||
Hoon has almost 90 digraphic runes. They are easier to organize
|
||||
in your head, though, because the choice of glyph is not random.
|
||||
The second glyph in a rune means little or nothing, but the first
|
||||
defines a rough semantic category. These categories are:
|
||||
|
||||
| bar gates (ie, functions) (ie, one-method cores)
|
||||
? wut conditionals, booleans, tests
|
||||
: col tuples
|
||||
. dot nock operators
|
||||
$ buc factory macros (ie, type definitions)
|
||||
^ ket type conversions
|
||||
= tis compositions
|
||||
% cen invocations
|
||||
& pam gears (ie, objects) (ie, multi-method cores)
|
||||
~ sig hints
|
||||
; sem miscellaneous macros
|
||||
! zap special operations
|
||||
|
||||
Each rune has _[not]_ its own doc file in the `rune/190` directory. The
|
||||
name of the file is the name of the rune, minus the vowels.
|
||||
Thus, `|=` or "bartis" is _[not]_ defined in `rune/190/brts.txt`.
|
||||
|
||||
Opening this file, we see:
|
||||
|
||||
%brts |= "bartis"
|
||||
|
||||
define:
|
||||
[%brts p=gene q=gene]
|
||||
|
||||
expand:
|
||||
[%brts *] [%brcb p.gen (~(put by *(map term foot)) %% [%ash q.gen])]
|
||||
|
||||
There should be some actual discussion, but there isn't. Still,
|
||||
`brts.txt` is quite complete as a definition of `|=`. How? We
|
||||
need to step back a little.
|
||||
|
||||
When the Hoon parser parses a source file, it generates a noun
|
||||
called a `gene`. If you know what an AST is, a gene is an AST node.
|
||||
If you don't, don't worry about it.
|
||||
|
||||
Search the current kernel for `++ gene` - note double space.
|
||||
This code is both the type declaration for type `gene`, and
|
||||
a function that maps an untyped noun to a typed gene. In it
|
||||
you'll see the above definition,
|
||||
|
||||
[%brts p=gene q=gene]
|
||||
|
||||
Ie, one kind of gene is a triple whose head is the constant
|
||||
`%brts`, and whose tail is a pair of genes, `p` and `q`.
|
||||
|
||||
We also see the semantics of this rune: it expands to
|
||||
|
||||
[%brcb p.gen (~(put by *(map term foot)) %% [%ash q.gen])]
|
||||
|
||||
ie, `|=` is a built-in macro. But back to syntax.
|
||||
|
||||
What is `%brts`? The atom also known as `1937011298` or
|
||||
`0x73747262`. Simply a string mapped to an unsigned integer, LSB
|
||||
first. It's easy to see why the vowels got lost - `%bartis` is
|
||||
`126896762413410` or `0x736974726162`. On a 32-bit CPU with
|
||||
31-bit direct atoms, `%brts` is direct and `%bartis` indirect
|
||||
(not that the programmer can tell the difference). But you still
|
||||
say "bartis."
|
||||
|
||||
For instance, in the decrement above, we have
|
||||
|
||||
|= a=@
|
||||
~| 'dec'
|
||||
^- @
|
||||
?< =(0 a)
|
||||
=+ b=@
|
||||
|-
|
||||
?: =(a +(b))
|
||||
b
|
||||
$(b +(b))
|
||||
|
||||
In this `%brts`, `p` is
|
||||
|
||||
a=@
|
||||
|
||||
and `q` is
|
||||
|
||||
~| 'dec'
|
||||
^- @
|
||||
?< =(0 a)
|
||||
=+ b=@
|
||||
|-
|
||||
?: =(a +(b))
|
||||
b
|
||||
$(b +(b))
|
||||
|
||||
We are starting to see the principles of Hoon syntax. Let's make
|
||||
them clear.
|
||||
|
||||
First, for any rune, the Hoon parser has two kinds of syntax:
|
||||
normal and custom. Most runes, such as `|=`, have only normal
|
||||
syntax without custom syntax. Almost all runes with custom
|
||||
syntax also have normal. Custom can mean anything; normal
|
||||
is rigid and uniform.
|
||||
|
||||
All programming languages, but especially functional ones, face
|
||||
two difficult syntactic problems. One is controlling the large
|
||||
numbers of terminators that appear in any deeply nested tree
|
||||
structure - Lisp is infamous for its piles of right parens.
|
||||
These are not a serious usability problem, except inasmuch as you
|
||||
consider ugly a usability problem (which I do). Two, a more
|
||||
serious concern, is keeping complex routines from flowing off the
|
||||
right margin as tab depth increases.
|
||||
|
||||
A glance at the more complex organs of the Hoon kernel reveals
|
||||
that Hoon is relatively untroubled by either of these woes. But
|
||||
why? One dubious panacea for the terminator problem is the use
|
||||
of significant whitespace. Whitespace in Hoon is not
|
||||
significant. (To be exact, the presence or absence of whitespace
|
||||
matters, but the quantity never does.)
|
||||
|
||||
The answer is that the normal syntax for every rune has two
|
||||
forms: "wide" and "tall." As a functional language, Hoon does
|
||||
not distinguish between statements and expressions, but normal
|
||||
wide syntax is expression-like and tall is statement-like.
|
||||
|
||||
For instance, in our example above,
|
||||
|
||||
?: =(a +(b))
|
||||
b
|
||||
$(b +(b))
|
||||
|
||||
is a tall normal form. The equivalent wide form is
|
||||
|
||||
?:(=(a +(b)) b $(b +(b)))
|
||||
|
||||
It's usually best to use the wide form if your gene fits on the
|
||||
line, but this is obviously an aesthetic choice. If your gene
|
||||
does not fit your margin (which should always be 80 columns),
|
||||
you have no choice but to go tall. For reasons that should be
|
||||
obvious, a tall gene can contain wide subgenes, but a wide gene
|
||||
cannot contain tall subgenes - just as, in procedural languages,
|
||||
a statement can contain expressions but not vice versa.
|
||||
|
||||
In the wide normal form, the rune is followed immediately (no
|
||||
whitespace) by a left paren ("pel"), then the subgenes with a
|
||||
single space between them, then a right paren ("per") as
|
||||
terminator. If the rune was inside the parens rather than a
|
||||
prefix, this would be the Lisp syntax.
|
||||
|
||||
In the tall normal form, any quantity of whitespace follows the
|
||||
rune, and separates the subgenes from each other. Where is the
|
||||
terminator? There is no terminator - in most cases.
|
||||
|
||||
Consider the `?:` rune, "wutcol," `%wtcl`. This is
|
||||
|
||||
[%wtcl p=gene q=gene r=gene]
|
||||
|
||||
Why should we need a terminator? We know `%wtcl`, whose
|
||||
semantics are if-then-else, has three subgenes. When the parser
|
||||
sees `?:` followed by space, it simply parses the next three
|
||||
genes and fills the rune with them.
|
||||
|
||||
This only works in runes with fixed tuple structure, which
|
||||
fortunately is most of them. A counterexample is `:*`, ie,
|
||||
|
||||
[%cltr p=(list gene)]
|
||||
|
||||
which is of variable length and needs a terminator. But we
|
||||
have no dangling parens, but an attractive tall closure:
|
||||
|
||||
:* %foo
|
||||
%bar
|
||||
%baz
|
||||
%moo
|
||||
==
|
||||
|
||||
whose equivalent wide normal is
|
||||
|
||||
:*(%foo %bar %baz %moo)
|
||||
|
||||
which no one would ever write, preferring the custom
|
||||
|
||||
[%foo %bar %baz %moo]
|
||||
|
||||
This leaves only one question: indentation. Since space is not
|
||||
significant (even linebreaks are irrelevant - the newline is just
|
||||
another space), the use of whitespace in tall forms is purely a
|
||||
matter of style. Style is very important, however!
|
||||
|
||||
The first law of Hoon indentation style is that all tall
|
||||
indentation is in two-space increments. (Tabs are illegal. If
|
||||
you pollute a Hoon file with ASCII 9, not only will it not parse,
|
||||
but thugs in ski masks will kick down your door and shoot you.
|
||||
You laugh! Try it!) Single spaces are for wide only.
|
||||
|
||||
The second law of Hoon indentation is that everything in the
|
||||
kernel is good indentation style. Or at least if it's not, it
|
||||
needs changed. The kernel shall be lapidary, noble, ideal and
|
||||
above all suspicion - a Doric column, a Tlingit totem pole,
|
||||
an Egyptian obelisk.
|
||||
|
||||
Tallness matters. The third law of Hoon indentation is that
|
||||
large genes should flow _down_ and not _across_ - like the
|
||||
decrement example above. The right margin is a precious resource
|
||||
not to be wasted. It's this law, when properly applied, that
|
||||
makes casual readers wonder if Hoon is a functional language at
|
||||
all. It doesn't have a program counter, but it looks like it
|
||||
does - at least when written right.
|
||||
|
||||
In list-structured runes, like the `:*` above, there is no choice
|
||||
but to lose right margin. Fortunately, most runes are tuples,
|
||||
and most have limited "fanout" - 1, 2, 3 or at most 4.
|
||||
|
||||
Both of our above examples - `|=` and `?:` - use "backstep"
|
||||
indentation which takes advantage of this tuple structure. For
|
||||
instance, `|=` has two subgenes, `p` and `q.` We put `p` on the
|
||||
same line as `|=`, set off by two spaces, losing 4 characters of
|
||||
margin. We put `q` _directly below_, losing no margin at all.
|
||||
|
||||
It so happens that in almost every actual case of `|=`, `p` (the
|
||||
function's argument) is relatively light, whereas `q` (the
|
||||
function's body) will be much heavier. Thus, with this pattern of
|
||||
indentation, we lose no margin and our code flows _down_.
|
||||
|
||||
We see this even more in `?:`, where the conditional test (which
|
||||
is much less likely to be heavy) is first and farthest right,
|
||||
followed by the "then" case indented two spaces, followed by the
|
||||
"else" case at the same indent as the rune.
|
||||
|
||||
Suppose your "else" is relatively light, and your "then" is
|
||||
heavy? You may prefer the `?.` rune, Hoon's "unless," which puts
|
||||
the else before the then. Or not. And in both `?:` and `?.`,
|
||||
the test (which of course can be arbitrarily heavy) is first.
|
||||
It is not necessary for your code to _always_ flow down and not
|
||||
across - just _mostly_.
|
||||
|
||||
The conventional principle which backstep indentation sacrifices,
|
||||
of course, is the idea that absolute indentation depth should
|
||||
correspond to tree depth, loop depth, or some other metric. Hoon
|
||||
is so deeply nested that if tab depth matched tree depth, your
|
||||
margins on anything interesting would be in the next cube to your
|
||||
right. There is perhaps a case for indenting loops, but we don't
|
||||
find we miss this cue at all.
|
||||
|
||||
The paucity of terminators also eliminates a lot of redundancy in
|
||||
the parser, which can result in relatively exciting syntax
|
||||
errors. Our experience is that this is seldom a big problem,
|
||||
because there are terminated tall forms and the cascade stops
|
||||
with them. It is often a small problem, however.
|
@ -1,382 +0,0 @@
|
||||
4: Hoon - Semantics
|
||||
=========
|
||||
|
||||
Now let's actually learn to program in Hoon, which really isn't
|
||||
that hard. Unfortunately, we have to start by learning enough
|
||||
Arvo to not be utterly defenseless.
|
||||
|
||||
Start vere, the Arvo VM, which you created in chapter 2:
|
||||
|
||||
vere $mypier
|
||||
|
||||
You'll see a prompt of the form
|
||||
|
||||
~tasfyn-partyv/try=>
|
||||
|
||||
What does this prompt mean?
|
||||
|
||||
`~tasfyn-partyv` (or whatever) is your ship. `try` is your desk;
|
||||
that is, the project you're working on. There is no spur, or
|
||||
further current directory. The current case (revision) is the
|
||||
null case, `=`, ie, you are working in the present tense.
|
||||
|
||||
This is the `behn` prompt. Arvo is talking to you through the
|
||||
console vane `dill` from the processing vane `behn`. You can
|
||||
think of `behn` as a shell, a REPL or both. As either it is very
|
||||
barebones and can perform only a few basic tasks. You can see
|
||||
its source in Unix in `$URBIT_HOME/tasfyn-partyv/arvo/behn.hoon`,
|
||||
or print it in Urbit with `:cat /===/arvo/behn/hoon`.
|
||||
|
||||
As this correspondence implies, basically the way you get
|
||||
any kind of bulk information from Unix, into Urbit, is to write
|
||||
it into a Unix file whose path Urbit knows how to sync.
|
||||
|
||||
While learning Hoon we are not going to run any commands in
|
||||
`behn` or use its OS-level capabilities at all. We are just
|
||||
going to use it as a REPL. But we are still using it and should
|
||||
have no reason to fear it.
|
||||
|
||||
`behn` uses emacs control keys for history and editing.
|
||||
^ABEFKNPY all basically kind of work. (Since `dill` works at the
|
||||
character level, not the line level, this is not `readline`.)
|
||||
There are also special Arvo controls: ^CDWX.
|
||||
|
||||
^C will stop any working task (and cancel the event that caused
|
||||
it). If Urbit is waiting rather than working, ^C will kill the
|
||||
waiting task. ^X will cycle between waiting tasks and the
|
||||
command line (which can always create a new task). Think of it
|
||||
as switching windows, in a command-line sense. ^W switches
|
||||
ships, on a multi-ship pier. ^D exits back to Unix.
|
||||
|
||||
If there is an error you may see a stack trace. The bottom of
|
||||
the trace is the thing that caused the problem.
|
||||
|
||||
|
||||
|
||||
Now let's see how to use Hoon
|
||||
|
||||
|
||||
|
||||
Now, let's actually look at Hoon. Really, try not to recoil
|
||||
in horror. It's actually not anything like line noise.
|
||||
|
||||
Open the Hoon kernel - `urb/les/arvo/hoon.hoon`. Let's look at
|
||||
the full, official decrement function (line 549):
|
||||
|
||||
++ dec
|
||||
~/ %dec
|
||||
|= a=@
|
||||
^- @
|
||||
?< =(0 a)
|
||||
=+ b=@
|
||||
|-
|
||||
?: =(a +(b))
|
||||
b
|
||||
$(b +(b))
|
||||
|
||||
Whaa?
|
||||
|
||||
Any attempt to understand this in terms of any language you
|
||||
already know would clearly be mistaken. In both syntax and
|
||||
semantics, learning to program in Hoon is learning to program all
|
||||
over again. When we say from scratch, we mean from scratch!
|
||||
|
||||
It's actually worse than that - learning Hoon is learning to
|
||||
_read_ all over again. Hoon is a keyword-free language - any
|
||||
alphanumeric text in the program is part of the program. Where
|
||||
other languages have reserved words, Hoon has squiggles.
|
||||
|
||||
We use so many of these ASCII glyphs that we like to be able
|
||||
to read them out loud. A language is meant to be _said_. The
|
||||
squiggles have conventional names, sort of, some of them, some of
|
||||
them easy to say, others not so much. So we've renamed them:
|
||||
|
||||
ace space gal < per )
|
||||
bar | gar > sel [
|
||||
bas \ hax # sem ;
|
||||
buc $ hep - ser ]
|
||||
cab _ kel { sig ~
|
||||
cen % ker } soq '
|
||||
col : ket ^ tar *
|
||||
com , lus + tec `
|
||||
doq " pam & tis =
|
||||
dot . pat @ wut ?
|
||||
fas / pel ( zap !
|
||||
|
||||
You just have to memorize these names. Sorry.
|
||||
|
||||
But is this at least enough symbols? Alas, nowhere near.
|
||||
ASCII's glyph supply is not the greatest, but we can make all the
|
||||
squiggles we need by forming digraphs, or _runes_.
|
||||
|
||||
To pronounce a rune, concatenate the glyph names, stressing the
|
||||
first syllable and softening the second vowel into a "schwa."
|
||||
Hence, to say `~.`, say "sigdot." To say `|=`, say "bartis."
|
||||
Which has an inevitable tendency to turn into "barts" - a sin
|
||||
to be encouraged. In any language actually spoken by actual
|
||||
humans, laziness soon rounds off any rough edges.
|
||||
|
||||
So if we had to read the above decrement, omitting the spaces
|
||||
(which only a real purist would pronounce), we'd say: "luslus dec
|
||||
sigfas cen dec bartis a tis pat sigbar soq dec soq ketcab pat wutgal
|
||||
tis pel zero a per tislus b tis pat barhep wutcol tis pel a lus pel
|
||||
b per per b buc pel b lus pel b per per." The authorities would
|
||||
then arrive, and drag us out in a big net. Definitely don't do
|
||||
this at the airport.
|
||||
|
||||
Geeks being solitary by nature, opportunities for reading code
|
||||
aloud are limited. But studies by actual scientists have shown
|
||||
that even when we read silently, we activate the motor cortex
|
||||
that controls our vocal cords. Even if we never speak these
|
||||
squiggles, they're easier to _think_ if bound to simple sounds.
|
||||
|
||||
(And don't worry if you can't get yourself to say "lus" instead
|
||||
of "plus" for `+`, or "tar" instead of "star" for `*` - I have
|
||||
this problem myself. It's much easier to replace "underscore"
|
||||
with "cab" or "ampersand" with "pam.")
|
||||
|
||||
Hoon has almost 90 digraphic runes. They are easier to organize
|
||||
in your head, though, because the choice of glyph is not random.
|
||||
The second glyph in a rune means little or nothing, but the first
|
||||
defines a rough semantic category. These categories are:
|
||||
|
||||
| bar gates (ie, functions) (ie, one-method cores)
|
||||
? wut conditionals, booleans, tests
|
||||
: col tuples
|
||||
. dot nock operators
|
||||
$ buc factory macros (ie, type definitions)
|
||||
^ ket type conversions
|
||||
= tis compositions
|
||||
% cen invocations
|
||||
& pam gears (ie, objects) (ie, multi-method cores)
|
||||
~ sig hints
|
||||
; sem miscellaneous macros
|
||||
! zap special operations
|
||||
|
||||
Each rune has _[not]_ its own doc file in the `rune/190` directory. The
|
||||
name of the file is the name of the rune, minus the vowels.
|
||||
Thus, `|=` or "bartis" is _[not]_ defined in `rune/190/brts.txt`.
|
||||
|
||||
Opening this file, we see:
|
||||
|
||||
%brts |= "bartis"
|
||||
|
||||
define:
|
||||
[%brts p=gene q=gene]
|
||||
|
||||
expand:
|
||||
[%brts *] [%brcb p.gen (~(put by *(map term foot)) %% [%ash q.gen])]
|
||||
|
||||
There should be some actual discussion, but there isn't. Still,
|
||||
`brts.txt` is quite complete as a definition of `|=`. How? We
|
||||
need to step back a little.
|
||||
|
||||
When the Hoon parser parses a source file, it generates a noun
|
||||
called a `gene`. If you know what an AST is, a gene is an AST node.
|
||||
If you don't, don't worry about it.
|
||||
|
||||
Search the current kernel for `++ gene` - note double space.
|
||||
This code is both the type declaration for type `gene`, and
|
||||
a function that maps an untyped noun to a typed gene. In it
|
||||
you'll see the above definition,
|
||||
|
||||
[%brts p=gene q=gene]
|
||||
|
||||
Ie, one kind of gene is a triple whose head is the constant
|
||||
`%brts`, and whose tail is a pair of genes, `p` and `q`.
|
||||
|
||||
We also see the semantics of this rune: it expands to
|
||||
|
||||
[%brcb p.gen (~(put by *(map term foot)) %% [%ash q.gen])]
|
||||
|
||||
ie, `|=` is a built-in macro. But back to syntax.
|
||||
|
||||
What is `%brts`? The atom also known as `1937011298` or
|
||||
`0x73747262`. Simply a string mapped to an unsigned integer, LSB
|
||||
first. It's easy to see why the vowels got lost - `%bartis` is
|
||||
`126896762413410` or `0x736974726162`. On a 32-bit CPU with
|
||||
31-bit direct atoms, `%brts` is direct and `%bartis` indirect
|
||||
(not that the programmer can tell the difference). But you still
|
||||
say "bartis."
|
||||
|
||||
For instance, in the decrement above, we have
|
||||
|
||||
|= a=@
|
||||
~| 'dec'
|
||||
^- @
|
||||
?< =(0 a)
|
||||
=+ b=@
|
||||
|-
|
||||
?: =(a +(b))
|
||||
b
|
||||
$(b +(b))
|
||||
|
||||
In this `%brts`, `p` is
|
||||
|
||||
a=@
|
||||
|
||||
and `q` is
|
||||
|
||||
~| 'dec'
|
||||
^- @
|
||||
?< =(0 a)
|
||||
=+ b=@
|
||||
|-
|
||||
?: =(a +(b))
|
||||
b
|
||||
$(b +(b))
|
||||
|
||||
We are starting to see the principles of Hoon syntax. Let's make
|
||||
them clear.
|
||||
|
||||
First, for any rune, the Hoon parser has two kinds of syntax:
|
||||
normal and custom. Most runes, such as `|=`, have only normal
|
||||
syntax without custom syntax. Almost all runes with custom
|
||||
syntax also have normal. Custom can mean anything; normal
|
||||
is rigid and uniform.
|
||||
|
||||
All programming languages, but especially functional ones, face
|
||||
two difficult syntactic problems. One is controlling the large
|
||||
numbers of terminators that appear in any deeply nested tree
|
||||
structure - Lisp is infamous for its piles of right parens.
|
||||
These are not a serious usability problem, except inasmuch as you
|
||||
consider ugly a usability problem (which I do). Two, a more
|
||||
serious concern, is keeping complex routines from flowing off the
|
||||
right margin as tab depth increases.
|
||||
|
||||
A glance at the more complex organs of the Hoon kernel reveals
|
||||
that Hoon is relatively untroubled by either of these woes. But
|
||||
why? One dubious panacea for the terminator problem is the use
|
||||
of significant whitespace. Whitespace in Hoon is not
|
||||
significant. (To be exact, the presence or absence of whitespace
|
||||
matters, but the quantity never does.)
|
||||
|
||||
The answer is that the normal syntax for every rune has two
|
||||
forms: "wide" and "tall." As a functional language, Hoon does
|
||||
not distinguish between statements and expressions, but normal
|
||||
wide syntax is expression-like and tall is statement-like.
|
||||
|
||||
For instance, in our example above,
|
||||
|
||||
?: =(a +(b))
|
||||
b
|
||||
$(b +(b))
|
||||
|
||||
is a tall normal form. The equivalent wide form is
|
||||
|
||||
?:(=(a +(b)) b $(b +(b)))
|
||||
|
||||
It's usually best to use the wide form if your gene fits on the
|
||||
line, but this is obviously an aesthetic choice. If your gene
|
||||
does not fit your margin (which should always be 80 columns),
|
||||
you have no choice but to go tall. For reasons that should be
|
||||
obvious, a tall gene can contain wide subgenes, but a wide gene
|
||||
cannot contain tall subgenes - just as, in procedural languages,
|
||||
a statement can contain expressions but not vice versa.
|
||||
|
||||
In the wide normal form, the rune is followed immediately (no
|
||||
whitespace) by a left paren ("pel"), then the subgenes with a
|
||||
single space between them, then a right paren ("per") as
|
||||
terminator. If the rune was inside the parens rather than a
|
||||
prefix, this would be the Lisp syntax.
|
||||
|
||||
In the tall normal form, any quantity of whitespace follows the
|
||||
rune, and separates the subgenes from each other. Where is the
|
||||
terminator? There is no terminator - in most cases.
|
||||
|
||||
Consider the `?:` rune, "wutcol," `%wtcl`. This is
|
||||
|
||||
[%wtcl p=gene q=gene r=gene]
|
||||
|
||||
Why should we need a terminator? We know `%wtcl`, whose
|
||||
semantics are if-then-else, has three subgenes. When the parser
|
||||
sees `?:` followed by space, it simply parses the next three
|
||||
genes and fills the rune with them.
|
||||
|
||||
This only works in runes with fixed tuple structure, which
|
||||
fortunately is most of them. A counterexample is `:*`, ie,
|
||||
|
||||
[%cltr p=(list gene)]
|
||||
|
||||
which is of variable length and needs a terminator. But we
|
||||
have no dangling parens, but an attractive tall closure:
|
||||
|
||||
:* %foo
|
||||
%bar
|
||||
%baz
|
||||
%moo
|
||||
==
|
||||
|
||||
whose equivalent wide normal is
|
||||
|
||||
:*(%foo %bar %baz %moo)
|
||||
|
||||
which no one would ever write, preferring the custom
|
||||
|
||||
[%foo %bar %baz %moo]
|
||||
|
||||
This leaves only one question: indentation. Since space is not
|
||||
significant (even linebreaks are irrelevant - the newline is just
|
||||
another space), the use of whitespace in tall forms is purely a
|
||||
matter of style. Style is very important, however!
|
||||
|
||||
The first law of Hoon indentation style is that all tall
|
||||
indentation is in two-space increments. (Tabs are illegal. If
|
||||
you pollute a Hoon file with ASCII 9, not only will it not parse,
|
||||
but thugs in ski masks will kick down your door and shoot you.
|
||||
You laugh! Try it!) Single spaces are for wide only.
|
||||
|
||||
The second law of Hoon indentation is that everything in the
|
||||
kernel is good indentation style. Or at least if it's not, it
|
||||
needs changed. The kernel shall be lapidary, noble, ideal and
|
||||
above all suspicion - a Doric column, a Tlingit totem pole,
|
||||
an Egyptian obelisk.
|
||||
|
||||
Tallness matters. The third law of Hoon indentation is that
|
||||
large genes should flow _down_ and not _across_ - like the
|
||||
decrement example above. The right margin is a precious resource
|
||||
not to be wasted. It's this law, when properly applied, that
|
||||
makes casual readers wonder if Hoon is a functional language at
|
||||
all. It doesn't have a program counter, but it looks like it
|
||||
does - at least when written right.
|
||||
|
||||
In list-structured runes, like the `:*` above, there is no choice
|
||||
but to lose right margin. Fortunately, most runes are tuples,
|
||||
and most have limited "fanout" - 1, 2, 3 or at most 4.
|
||||
|
||||
Both of our above examples - `|=` and `?:` - use "backstep"
|
||||
indentation which takes advantage of this tuple structure. For
|
||||
instance, `|=` has two subgenes, `p` and `q.` We put `p` on the
|
||||
same line as `|=`, set off by two spaces, losing 4 characters of
|
||||
margin. We put `q` _directly below_, losing no margin at all.
|
||||
|
||||
It so happens that in almost every actual case of `|=`, `p` (the
|
||||
function's argument) is relatively light, whereas `q` (the
|
||||
function's body) will be much heavier. Thus, with this pattern of
|
||||
indentation, we lose no margin and our code flows _down_.
|
||||
|
||||
We see this even more in `?:`, where the conditional test (which
|
||||
is much less likely to be heavy) is first and farthest right,
|
||||
followed by the "then" case indented two spaces, followed by the
|
||||
"else" case at the same indent as the rune.
|
||||
|
||||
Suppose your "else" is relatively light, and your "then" is
|
||||
heavy? You may prefer the `?.` rune, Hoon's "unless," which puts
|
||||
the else before the then. Or not. And in both `?:` and `?.`,
|
||||
the test (which of course can be arbitrarily heavy) is first.
|
||||
It is not necessary for your code to _always_ flow down and not
|
||||
across - just _mostly_.
|
||||
|
||||
The conventional principle which backstep indentation sacrifices,
|
||||
of course, is the idea that absolute indentation depth should
|
||||
correspond to tree depth, loop depth, or some other metric. Hoon
|
||||
is so deeply nested that if tab depth matched tree depth, your
|
||||
margins on anything interesting would be in the next cube to your
|
||||
right. There is perhaps a case for indenting loops, but we don't
|
||||
find we miss this cue at all.
|
||||
|
||||
The paucity of terminators also eliminates a lot of redundancy in
|
||||
the parser, which can result in relatively exciting syntax
|
||||
errors. Our experience is that this is seldom a big problem,
|
||||
because there are terminated tall forms and the cascade stops
|
||||
with them. It is often a small problem, however.
|
@ -1,8 +0,0 @@
|
||||
|= %brbn "barbon"
|
||||
|
||||
gene:
|
||||
[%brbn p=*gene q=*gene]
|
||||
|
||||
ap open:
|
||||
[%pmbn p.gen [[%% [& q.gen]] ~ ~]]
|
||||
--
|
@ -1,14 +0,0 @@
|
||||
|? %brcs "barcas"
|
||||
|
||||
gene:
|
||||
[%brcs p=*(list gene)]
|
||||
|
||||
ap open:
|
||||
?~ p.gen
|
||||
[%zpzp ~]
|
||||
[%brbn [%ktdp [%tmbn %noun] i.p.gen] [%cstr [~ 4] p.gen]]
|
||||
|
||||
meta:
|
||||
?~ p.gen
|
||||
!$(!!)
|
||||
!$(|=(^-(* {i.p.gen}) ?*(.4 {p.gen})))
|
@ -1,10 +0,0 @@
|
||||
|- %brdp "bardap"
|
||||
|
||||
gene:
|
||||
[%brdp p=*gene]
|
||||
|
||||
ap open:
|
||||
[%bnld [%brdg p.gen] %%]
|
||||
|
||||
meta:
|
||||
=>(|:({p.gen}) $)
|
@ -1,7 +0,0 @@
|
||||
|: %brdg "bardig"
|
||||
|
||||
gene:
|
||||
[%brdg p=*gene]
|
||||
|
||||
ap open:
|
||||
[%pmdg [[%% [& p.gen]] ~ ~]]
|
@ -1,7 +0,0 @@
|
||||
|. %brdt "bardot"
|
||||
|
||||
gene:
|
||||
[%brdt p=*gene q=*gene]
|
||||
|
||||
ap open:
|
||||
[%pmdt p.gen [[%% [& q.gen]] ~ ~]]
|
@ -1,8 +0,0 @@
|
||||
|* %brtr "bartar"
|
||||
|
||||
gene:
|
||||
[%brtr p=*gene q=*gene]
|
||||
|
||||
ap open:
|
||||
[%pmtr p.gen [[%% [& q.gen]] ~ ~]]
|
||||
--
|
@ -1,10 +0,0 @@
|
||||
=- %bndp "bondap"
|
||||
|
||||
gene:
|
||||
[%bndp p=*gene q=*gene]
|
||||
|
||||
open:
|
||||
[%bnpd q.gen p.gen]
|
||||
|
||||
soft:
|
||||
=+({q.gen} {p.gen})
|
@ -1,10 +0,0 @@
|
||||
=< %bndl "bondel"
|
||||
|
||||
gene:
|
||||
[%bndl p=*gene q=*gene]
|
||||
|
||||
open:
|
||||
[%bnld q.gen p.gen]
|
||||
|
||||
soft:
|
||||
=>({q.gen} {p.gen})
|
@ -1,4 +0,0 @@
|
||||
=> %bnld "bonled"
|
||||
|
||||
gene:
|
||||
[%bnld p=*gene q=*gene]
|
@ -1,4 +0,0 @@
|
||||
=+ %bnpd "bonpad"
|
||||
|
||||
gene:
|
||||
[%bnpd p=*gene q=*gene]
|
@ -1,4 +0,0 @@
|
||||
?| %csbr "casbar"
|
||||
|
||||
gene:
|
||||
[%csbr p=*(list gene)]
|
@ -1,4 +0,0 @@
|
||||
?= %csbn "casbon"
|
||||
|
||||
gene:
|
||||
[%csbn p=*gene q=*gene]
|
@ -1,4 +0,0 @@
|
||||
?- %csdp "casdap"
|
||||
|
||||
gene:
|
||||
[%csdp p=*gene q=*(list &[p=*gene q=*gene])] :: ?- casdap
|
@ -1,4 +0,0 @@
|
||||
?: %csdg "casdeg"
|
||||
|
||||
gene:
|
||||
[%csdg p=*gene q=*gene r=*gene]
|
@ -1,4 +0,0 @@
|
||||
?< %csdl "casdel"
|
||||
|
||||
gene:
|
||||
[%csdl p=*gene q=*gene]
|
@ -1,4 +0,0 @@
|
||||
?. %csdt "casdot"
|
||||
|
||||
gene:
|
||||
[%csdt p=*gene q=*gene r=*gene]
|
@ -1,4 +0,0 @@
|
||||
?> %csld "casled"
|
||||
|
||||
gene:
|
||||
[%csld p=*gene q=*gene]
|
@ -1,4 +0,0 @@
|
||||
?& %cspm "caspam"
|
||||
|
||||
gene:
|
||||
[%cspm p=*(list gene)]
|
@ -1,4 +0,0 @@
|
||||
?~ %cssg "cassig"
|
||||
|
||||
gene:
|
||||
[%cssg p=*gene q=*gene r=*gene]
|
@ -1,4 +0,0 @@
|
||||
?* %cstr "castar"
|
||||
|
||||
gene:
|
||||
[%cstr p=*gene q=*(list gene)]
|
@ -1,4 +0,0 @@
|
||||
?! %cszp "caszap"
|
||||
|
||||
gene:
|
||||
[%cszp p=*gene]
|
@ -1,4 +0,0 @@
|
||||
:- %dgdp "degdap"
|
||||
|
||||
gene:
|
||||
[%dgdp p=*gene q=*gene]
|
@ -1,4 +0,0 @@
|
||||
:^ %dgkt "degket"
|
||||
|
||||
gene:
|
||||
[%dgkt p=*gene q=*gene r=*gene s=*gene]
|
@ -1,4 +0,0 @@
|
||||
:+ %dgpd "degpad"
|
||||
|
||||
gene:
|
||||
[%dgpd p=*gene q=*gene r=*gene]
|
@ -1,4 +0,0 @@
|
||||
:` %dgsg "degsig"
|
||||
|
||||
gene:
|
||||
[%dgsg p=*(list gene)]
|
@ -1,4 +0,0 @@
|
||||
:* %dgtr "degtar"
|
||||
|
||||
gene:
|
||||
[%dgtr p=*(list gene)]
|
@ -1,4 +0,0 @@
|
||||
.= %dtbn "dotbon"
|
||||
|
||||
gene:
|
||||
[%dtbn p=*gene q=*gene]
|
@ -1,4 +0,0 @@
|
||||
.^ %dtkt "dotket"
|
||||
|
||||
gene:
|
||||
[%dtkt p=*gene]
|
@ -1 +0,0 @@
|
||||
.~ %dtsg "dotsig"
|
@ -1,4 +0,0 @@
|
||||
.* %dttr "dottar"
|
||||
|
||||
gene:
|
||||
[%dttr p=*gene q=*gene]
|
@ -1,4 +0,0 @@
|
||||
^= %ktbn "ketbon"
|
||||
|
||||
gene:
|
||||
[%ktbn p=*term q=*gene]
|
@ -1,4 +0,0 @@
|
||||
^- %ktdp "ketdap"
|
||||
|
||||
gene:
|
||||
[%ktdp p=*gene q=*gene]
|
@ -1,4 +0,0 @@
|
||||
^+ %ktpd "ketpad"
|
||||
|
||||
gene:
|
||||
[%ktpd p=*gene q=*gene]
|
@ -1,4 +0,0 @@
|
||||
^~ %ktsg "ketsig"
|
||||
|
||||
gene:
|
||||
[%ktsg p=*gene]
|
@ -1,4 +0,0 @@
|
||||
%| %mtbr "mitbar"
|
||||
|
||||
gene:
|
||||
[%mtbr p=*twig q=*gene r=*(list &[p=*gene q=*gene])]
|
@ -1,4 +0,0 @@
|
||||
%= %mtbn "mitbon"
|
||||
|
||||
gene:
|
||||
[%mtbn p=*twig q=*(list &[p=*gene q=*gene])]
|
@ -1,4 +0,0 @@
|
||||
%- "mitdap" %mtdp
|
||||
|
||||
gene:
|
||||
[%mtdp p=*gene q=*(list gene)]
|
@ -1,4 +0,0 @@
|
||||
%: %mtdg "mitdeg"
|
||||
|
||||
gene:
|
||||
[%mtdt p=*gene q=*gene]
|
@ -1,4 +0,0 @@
|
||||
%. %mtdt "mitdot"
|
||||
|
||||
gene:
|
||||
[%mtdt p=*gene q=*gene]
|
@ -1,4 +0,0 @@
|
||||
%^ %mtkt "mitket"
|
||||
|
||||
gene:
|
||||
[%mtkt p=*gene q=*gene r=*gene s=*gene]
|
@ -1,4 +0,0 @@
|
||||
%+ %mtpd "mitpad"
|
||||
|
||||
gene:
|
||||
[%mtpd p=*gene q=*gene r=*gene]
|
@ -1,4 +0,0 @@
|
||||
%~ %mtsg "mitsig"
|
||||
|
||||
gene:
|
||||
[%mtsg p=*twig q=*gene r=*gene]
|
@ -1,4 +0,0 @@
|
||||
%* %mttr "mittar"
|
||||
|
||||
gene:
|
||||
[%mttr p=*gene]
|
@ -1,7 +0,0 @@
|
||||
&= %pmbn "pambon"
|
||||
|
||||
gene:
|
||||
[%pmtr p=*gene q=*(map term foot)]
|
||||
|
||||
ap open:
|
||||
[%bnpd p.gen [%pmzp %gold q.gen]]
|
@ -1,7 +0,0 @@
|
||||
&- %pmdp "pamdap"
|
||||
|
||||
gene:
|
||||
[%pmdp p=*gene q=*(map term foot)]
|
||||
|
||||
ap open:
|
||||
[%bnld [%pmdg q.gen] p.gen]
|
@ -1,7 +0,0 @@
|
||||
&: %pmdg "pamdeg"
|
||||
|
||||
gene:
|
||||
[%pmdg p=*(map term foot)]
|
||||
|
||||
ap open:
|
||||
[%pmzp %lead p.gen]
|
@ -1,7 +0,0 @@
|
||||
&. %pmdt "pamdot"
|
||||
|
||||
gene:
|
||||
[%pmdt p=*gene q=*(map term foot)]
|
||||
|
||||
ap open:
|
||||
[%bnpd p.gen [%pmzp %zinc q.gen]]
|
@ -1,7 +0,0 @@
|
||||
&* %pmtr "pamtar"
|
||||
|
||||
gene:
|
||||
[%pmtr p=*gene q=*(map term foot)]
|
||||
|
||||
ap open:
|
||||
[%bnpd p.gen [%pmzp %wood q.gen]]
|
@ -1,4 +0,0 @@
|
||||
&= %pmzp "pamzap"
|
||||
|
||||
gene:
|
||||
[%pmbn p=*gene q=*(map term foot)]
|
@ -1,7 +0,0 @@
|
||||
~| %sgbr "sigbar"
|
||||
|
||||
gene:
|
||||
[%sgbr p=*gene q=*gene]
|
||||
|
||||
ap open:
|
||||
[%sgld [%bean p.gen] q.gen]
|
@ -1,7 +0,0 @@
|
||||
~= %sgbn "sigbon"
|
||||
|
||||
gene:
|
||||
[%sgbn p=*gene q=*gene] :: ~= sigbon
|
||||
|
||||
ap open:
|
||||
[%sgld [%germ p.gen] q.gen]
|
@ -1,7 +0,0 @@
|
||||
~- %sgdp "sigdap"
|
||||
|
||||
gene:
|
||||
[%sgdp p=@ q=*gene]
|
||||
|
||||
ap open:
|
||||
[%sgld [%sole %dtsg p.gen] q.gen]
|
@ -1,7 +0,0 @@
|
||||
~^ %sgdx "sigdax"
|
||||
|
||||
gene:
|
||||
[%sgdx p=*gene]
|
||||
|
||||
ap open:
|
||||
[%sgld %ping p.gen]
|
@ -1,7 +0,0 @@
|
||||
~: %sgdg "sigdeg"
|
||||
|
||||
gene:
|
||||
[%sgdg p=[p=@ q=@] q=*gene]
|
||||
|
||||
ap open:
|
||||
[%sgld [%bank %dtsg p.gen] q.gen]
|
@ -1,7 +0,0 @@
|
||||
~< %sgdl "sigdel"
|
||||
|
||||
gene:
|
||||
[%sgdl p=*term q=*gene r=*gene]
|
||||
|
||||
ap open:
|
||||
[%bndl [%sgld p.gen [~ 1]] q.gen]
|
@ -1,7 +0,0 @@
|
||||
~^ %sgkt "sigket"
|
||||
|
||||
gene:
|
||||
[%sgkt p=*gene]
|
||||
|
||||
ap open:
|
||||
[%sgld %keep p.gen]
|
@ -1,4 +0,0 @@
|
||||
~> %sgld "sigled"
|
||||
|
||||
gene:
|
||||
[%sgld p=*term q=*gene r=*gene] :: ~> sigled
|
@ -1,17 +0,0 @@
|
||||
~% %sgmt "sigmit"
|
||||
|
||||
gene:
|
||||
[%sgmt p=*seal q=*gene r=*(list &[p=*term q=*gene]) s=*gene]
|
||||
|
||||
ap open:
|
||||
:+ %sgdl
|
||||
:- %mine
|
||||
:+ [%dtsg p.gen]
|
||||
[%zpbn q.gen]
|
||||
:- %dgsg
|
||||
=+ nob=`*(list gene)`~
|
||||
|- ^- nob
|
||||
?~ r.gen
|
||||
nob
|
||||
[[[%dtsg p.i.r.gen] [%zpbn q.i.r.gen]] $(r.gen t.r.gen)]
|
||||
q.gen
|
@ -1,7 +0,0 @@
|
||||
~+ %sgpd "sigpad"
|
||||
|
||||
gene:
|
||||
[%sgpd p=@ q=*gene]
|
||||
|
||||
ap open:
|
||||
[%sgld [%memo %dtsg p.gen] q.gen]
|
@ -1,7 +0,0 @@
|
||||
~& %sgpm "sigpam"
|
||||
|
||||
gene:
|
||||
[%sgpm p=*gene q=*gene]
|
||||
|
||||
ap open:
|
||||
[%sgld [%loaf p.gen] q.gen]
|
@ -1,4 +0,0 @@
|
||||
;= %tmbn "tambon"
|
||||
|
||||
gene:
|
||||
[%tmbn p=*<%atom %noun %cell %flag %null>]
|
@ -1,4 +0,0 @@
|
||||
;- %tmdp "tamdap"
|
||||
|
||||
gene:
|
||||
[%tmdp p=*gene]
|
@ -1,4 +0,0 @@
|
||||
;+ %tmpd "tampad"
|
||||
|
||||
gene:
|
||||
[%tmpd p=*gene q=*gene]
|
@ -1,110 +0,0 @@
|
||||
|~ %tmsg "tamsig"
|
||||
|
||||
gene:
|
||||
[%tmsg p=*gene q=*(list gene)]
|
||||
|
||||
ap open:
|
||||
|-
|
||||
?- q.gen
|
||||
~ !!
|
||||
[* ~] i.q.gen
|
||||
^
|
||||
:+ %bnpd
|
||||
$(q.gen t.q.gen)
|
||||
:+ %bnpd
|
||||
[%bnld [~ 3] i.q.gen]
|
||||
:+ %brbn
|
||||
[~ 8]
|
||||
:^ %mtpd
|
||||
[%bnld [~ 23] p.gen]
|
||||
[%mtdp [~ 10] [~ 4] ~]
|
||||
[%mtbn [[~ 22] ~] [[[~ 4] [~ 4]] ~]]
|
||||
--
|
||||
|
||||
ap open (meta):
|
||||
|-
|
||||
?- q.gen
|
||||
~ !!
|
||||
[* ~] i.q.gen
|
||||
^
|
||||
!$
|
||||
:: 1 == original subject
|
||||
::
|
||||
=+ {$(q.gen t.q.gen)}
|
||||
::
|
||||
:: 2 == {$(q.gen t.q.gen)}
|
||||
:: 3 == original subject
|
||||
::
|
||||
=+ =>(.3 {i.q.gen})
|
||||
::
|
||||
:: 2 == =>(.2 {i.q.gen})
|
||||
:: 3 == previous subject
|
||||
:: 6 == {$(q.gen t.q.gen)}
|
||||
:: 7 == original subject
|
||||
::
|
||||
|= .8
|
||||
::
|
||||
:: 4 == sample
|
||||
:: 5 == context
|
||||
:: 10 == =>(.2 {i.q.gen})
|
||||
:: 22 == {$(q.gen t.q.gen)}
|
||||
:: 23 == original subject
|
||||
:: 3 == formula
|
||||
::
|
||||
%+ =>(.23 {p.gen})
|
||||
%-(.10 .4)
|
||||
%=(.22 .4 .4)
|
||||
--
|
||||
|
||||
ap open (new):
|
||||
|-
|
||||
?- q.gen
|
||||
~ !!
|
||||
[* ~] i.q.gen
|
||||
^
|
||||
:+ %bnpd
|
||||
$(q.gen t.q.gen)
|
||||
:+ %bnpd
|
||||
[%bnld [~ 2] i.q.gen]
|
||||
:+ %brbn
|
||||
[~ 13]
|
||||
:^ %mtpd
|
||||
[%bnld [~ 16] p.gen]
|
||||
[%mtdp [~ 9] [~ 5] ~]
|
||||
[%mtbn [[~ 17] ~] [[[~ 5] [~ 5]] ~]]
|
||||
--
|
||||
|
||||
ap open (meta, new):
|
||||
|-
|
||||
?- q.gen
|
||||
~ !!
|
||||
[* ~] i.q.gen
|
||||
^
|
||||
!$
|
||||
:: 1 == original subject
|
||||
::
|
||||
=+ {$(q.gen t.q.gen)}
|
||||
::
|
||||
:: 2 == original subject
|
||||
:: 3 == {$(q.gen t.q.gen)}
|
||||
::
|
||||
=+ =>(.2 {i.q.gen})
|
||||
::
|
||||
:: 2 == previous subject
|
||||
:: 4 == original subject
|
||||
:: 5 == {$(q.gen t.q.gen)}
|
||||
:: 3 == =>(.2 {i.q.gen})
|
||||
::
|
||||
|= .13
|
||||
::
|
||||
:: 4 == context
|
||||
:: 16 == original subject
|
||||
:: 17 == {$(q.gen t.q.gen)}
|
||||
:: 9 == =>(.2 {i.q.gen})
|
||||
:: 5 == sample (.13)
|
||||
:: 3 == formula
|
||||
::
|
||||
%+ =>(.16 {p.gen})
|
||||
%-(.9 .5)
|
||||
%=(.17 .5 .5)
|
||||
--
|
@ -1,4 +0,0 @@
|
||||
!= %zpbn "zapbon"
|
||||
|
||||
gene:
|
||||
[%zpbn p=*gene]
|
@ -1,4 +0,0 @@
|
||||
!_ %zpcb "zapcab"
|
||||
|
||||
gene:
|
||||
[%zpcb p=*spot q=*gene]
|
@ -1,4 +0,0 @@
|
||||
!# %zpdx "zapdax"
|
||||
|
||||
gene:
|
||||
[%zpdx p=*gene]
|
@ -1,4 +0,0 @@
|
||||
!! %zpzp "zapzap"
|
||||
|
||||
gene:
|
||||
[%zpzp ~]
|
41
doc/sbox.txt
41
doc/sbox.txt
@ -1,41 +0,0 @@
|
||||
:: an invertible sbox for any conceivable use.
|
||||
|
||||
|%
|
||||
++ ice :: =(a (fry (ice a)))
|
||||
|= a=@
|
||||
=+ ^= b
|
||||
0x5aa2.c1dd.32d2.e2e4.5689.c39d.9584.dca4.3bda.9b47.a985.
|
||||
4f57.d313.77fb.1b6a.bafe.040b.43d1.8262.4db7.9926.90f7.
|
||||
bff4.7c09.c6df.ab87.3138.be29.eba5.1f1d.7a01.5e07.6769.
|
||||
003d.535d.518a.f97d.dbfd.1002.72b0.aff0.28f2.36b9.f122.
|
||||
b5e6.6ba0.3ac8.59f5.c0f3.65cb.976d.88d7.5ba7.3593.768c.
|
||||
7fa8.604a.98c5.de52.705f.0a55.4550.edc4.ae4e.647e.ce4b.
|
||||
cdb3.b1a1.e080.333c.96f8.c227.b2d5.e158.9cfa.4686.c7a6.
|
||||
e78f.1a74.4873.f616.0f66.63e8.6e54.1c49.062b.9271.9f78.
|
||||
05aa.ff12.e9b6.cfec.6fbd.1511.2523.41bb.14cc.c994.42d8.
|
||||
4c39.6c30.3e2a.acb8.242c.2f8d.83b4.19bc.ee81.3721.449a.
|
||||
757b.3fef.03d0.8bd4.a39e.0d8e.17d6.0efc.d91e.0861.ad18.
|
||||
ca91.e568.0c79.2e40.5cea.2034.2de3
|
||||
=+ c=(met 3 a)
|
||||
?> (lte c 1)
|
||||
(cut 3 [a 1] b)
|
||||
::
|
||||
++ fry
|
||||
|= a=@
|
||||
=+ ^= b
|
||||
0x4de0.b414.e46a.b772.d45f.a0d2.9eac.a9ae.2029.8348.c704.
|
||||
4b5a.65a6.0bf8.00f9.6d77.ce8b.fcf1.b5ee.133a.9816.6e1c.
|
||||
e7fa.dc1e.497d.7b3e.9c0d.3da2.67cf.8c82.f571.fd9f.d3c9.
|
||||
462a.40e1.aa32.d84a.a72c.7a6f.79b0.af81.0f33.cd4e.eb90.
|
||||
9666.c6f0.1bfe.78a4.511a.f46b.ed24.d78d.9b73.f33c.9453.
|
||||
0cd5.6418.2e92.1db8.f699.cc68.eaf2.2ddb.2876.917e.b6d1.
|
||||
22c3.0850.e593.2362.60b1.5289.4759.9a37.a5e2.be0a.bf5c.
|
||||
9d7f.5bda.108f.88c1.ba05.97ff.a16c.e8f7.8658.bb8a.b984.
|
||||
e980.d939.7c8e.5661.ec69.8525.dd3b.4106.2135.bc74.efa3.
|
||||
38ca.27ab.9502.75fb.cb36.2f07.0130.5434.c8ad.70d6.4331.
|
||||
42a8.2603.c512.c457.e363.2b0e.175e.453f.e64c.44b3.5d15.
|
||||
1909.de87.d011.c055.4fdf.1fb2.c2bd
|
||||
=+ c=(met 3 a)
|
||||
?> (lte c 1)
|
||||
(cut 3 [a 1] b)
|
||||
--
|
Loading…
Reference in New Issue
Block a user