mirror of
https://github.com/urbit/shrub.git
synced 2024-12-14 11:08:45 +03:00
Publishing 0-nouns.
This commit is contained in:
parent
84bae67929
commit
c27796a6ae
@ -7,8 +7,9 @@ sort: 2
|
||||
|
||||
# Developer documentation
|
||||
|
||||
Urbit has three programming layers: Nock (combinator nano-VM),
|
||||
Hoon (strict functional language), and Arvo (functional OS).
|
||||
Urbit has three programming layers: [Nock](dev/nock) (combinator nano-VM),
|
||||
[Hoon](dev/hoon) (strict functional language), and [Arvo](dev/arvo) (functional
|
||||
OS).
|
||||
|
||||
To code in Urbit, the least you need to learn is Hoon, plus a
|
||||
little bit of Arvo. Nock is a sort of functional assembly
|
||||
|
@ -7,8 +7,7 @@ sort: 3
|
||||
|
||||
# Arvo
|
||||
|
||||
Arvo is a functional operating system.
|
||||
|
||||
Watch this space for actual documentation.
|
||||
Arvo is a functional operating system. But hopefully you knew that! Sorry,
|
||||
please watch this space for actual documentation.
|
||||
|
||||
</div>
|
||||
|
@ -8,8 +8,8 @@ sort: 2
|
||||
|
||||
# Hoon
|
||||
|
||||
Hoon is a strict, typed, pure functional language.
|
||||
Hoon is a strict, typed, pure functional language.
|
||||
|
||||
Watch this space for actual documentation.
|
||||
We're still updating the docs; start with the tutorial, ["Hoon 101"](hoon/tutorial).
|
||||
|
||||
</div>
|
||||
|
@ -1,4 +0,0 @@
|
||||
Tutorials
|
||||
=========
|
||||
|
||||
<list dataPreview="true" titlesOnly="true"></list>
|
10
pub/docs/dev/hoon/tutorial.mdy
Normal file
10
pub/docs/dev/hoon/tutorial.mdy
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
logo: black
|
||||
title: Hoon 101
|
||||
sort: 2
|
||||
---
|
||||
|
||||
Welcome to Hoon 101! This tutorial is still under construction;
|
||||
check back later for more installments.
|
||||
|
||||
<list></list>
|
@ -1,232 +0,0 @@
|
||||
# Hoon 0: introduction
|
||||
|
||||
Hoon is a strict, higher-order typed pure-functional language.
|
||||
|
||||
Why Hoon? On the one hand, typed functional languages are known
|
||||
for a particularly pleasant phenomenon: once your code compiles,
|
||||
it's quite likely to work. On the other hand, most typed
|
||||
functional languages are influenced by advanced mathematics.
|
||||
As Barbie once put it, math class is hard.
|
||||
|
||||
Hoon is a typed FP language for the common street programmer.
|
||||
Well-written Hoon is as concrete and data-oriented as possible.
|
||||
The less functional magic you use, the better. One Haskell
|
||||
hacker described Hoon as "imperative programming in a functional
|
||||
language." He didn't mean this as a compliment, but we choose to
|
||||
take it as one.
|
||||
|
||||
Moreover, one task of a type system in network computing is
|
||||
marshalling typed data on the sender, and validating untrusted
|
||||
data on the receiver. Hoon is very good at this task, which in
|
||||
most typed languages is an afterthought at best.
|
||||
|
||||
The main disadvantage of Hoon is that its syntax and semantics
|
||||
are unfamiliar. The syntax will remind too many of Perl, but
|
||||
like most human languages (and unlike Perl) it combines a regular
|
||||
core structure with irregular variations. Its semantic
|
||||
complexity is bounded by the fact that the compiler is only 2000
|
||||
lines of Hoon (admittedly an expressive language). Most peoples'
|
||||
experience is that Hoon is much easier to learn than it looks.
|
||||
|
||||
## Nouns: data made boring
|
||||
|
||||
A noun is an atom or a cell. An atom is any unsigned integer. A
|
||||
cell is an ordered pair of nouns.
|
||||
|
||||
The noun is an intentionally boring data model. Nouns (at least,
|
||||
nouns in Urbit) don't have cycles (although a noun implementation
|
||||
should take advantage of acyclic graph structure). Noun
|
||||
comparison is always by value (there is no way for the programmer
|
||||
to test pointer equality). Nouns are strict; there is no such
|
||||
thing as an infinite noun. And, of course, nouns are immutable.
|
||||
So there's basically no way to have any real fun with nouns.
|
||||
|
||||
For language historians, nouns are Lisp's S-expressions, minus a
|
||||
lot of hacks, tricks, and features that made sense 50 years ago.
|
||||
In particular, because atoms are not tagged (an atom can encode a
|
||||
string, for instance), nouns work best with a static type system.
|
||||
How do you print an atom if you don't know whether it's a string
|
||||
or a number? You can guess, but...
|
||||
|
||||
## A type system for nouns
|
||||
|
||||
So learning nouns in practice involves learning them with a type
|
||||
system that makes them usable. Fortunately, we have that.
|
||||
|
||||
One obstacle to learning Hoon is that it has two quite distinct
|
||||
concepts that might equally be called a "type." Worse, most
|
||||
other typed functional languages are mathy and share a basically
|
||||
mathematical concept of "type." We can't avoid using the T-word
|
||||
occasionally, but it has no precise meaning in Hoon and can be
|
||||
extremely confusing.
|
||||
|
||||
Hoon's two kinds of "type" are `span` and `mold`. A span is both
|
||||
a constructively defined set of nouns, and a semantic convention
|
||||
for users in that set. A `mold` is a function whose range is
|
||||
some useful span. A mold is always idempotent (for any noun x,
|
||||
`f(x)` equals `f(f(x))`), and its domain is any noun.
|
||||
|
||||
(One way to explain this is that while a span is what most
|
||||
languages call a "type," Hoon has no way for the programmer to
|
||||
express a span directly. Instead, we use inference to define it
|
||||
as the range of a function. This same function, the mold, can
|
||||
also be used to validate or normalize untrusted, untyped data --
|
||||
a common problem in modern programming.)
|
||||
|
||||
(Hoon's inference algorithm is somewhat dumber than the
|
||||
unification algorithms (Hindley-Milner) used in most typed
|
||||
functional languages. Hoon reasons only forward, not backward.
|
||||
It needs more manual annotations, which you usually want anyway.
|
||||
Otherwise, it gets more or less the same job done.)
|
||||
|
||||
## Let's make some nouns
|
||||
|
||||
This stuff isn't even slightly hard. Let's make a noun:
|
||||
```
|
||||
~tasfyn-partyv:dojo> 42
|
||||
```
|
||||
You'll see the expression you entered, then the resulting value:
|
||||
```
|
||||
> 42
|
||||
42
|
||||
```
|
||||
Let's try a different value:
|
||||
```
|
||||
~tasfyn-partyv:dojo> 0x2a
|
||||
```
|
||||
You'll see:
|
||||
```
|
||||
> 0x2a
|
||||
0x2a
|
||||
```
|
||||
`42` and `0x2a` are actually *the same noun*, because they're the
|
||||
same number. But we don't just have the noun to print - we have
|
||||
a `[span noun]` cell (sometimes called a `vase`).
|
||||
|
||||
As you recall, a span defines a set of nouns and a semantic
|
||||
interpretation. As sets, both spans here are "any number". But
|
||||
semantically, `42` has a decimal span and `0x2a` hexadecimal, so
|
||||
they print differently.
|
||||
|
||||
(It's important to note that Hoon is a statically typed language.
|
||||
We don't work with vases unless we're dynamically compiling code,
|
||||
which is of course what we're doing here in the shell. Dynamic
|
||||
type is static type compiled at runtime.)
|
||||
|
||||
Finally, let's make some cells. Try these on your own ship:
|
||||
```
|
||||
~tasfyn-partyv:dojo> [42 0x2a]
|
||||
~tasfyn-partyv:dojo> [42 [0x2a 420]]
|
||||
~tasfyn-partyv:dojo> [42 0x2a 420]
|
||||
```
|
||||
We observe that cells associate right: `[a b c]` is just another
|
||||
way of writing `[a [b c]]`.
|
||||
|
||||
Also, Lisp veterans beware: Hoon `[a b]` is Lisp `(a . b)`, Lisp
|
||||
`(a b)` is Hoon `[a b ~]`(`~` represents nil, with a value of atom `0`). Lisp and Hoon are both pair-oriented
|
||||
languages down below, but Lisp has a layer of sugar that makes it
|
||||
look list-oriented. Hoon loves its "improper lists," ie, tuples.
|
||||
|
||||
## Looking at spans
|
||||
|
||||
What are these mysterious spans? We can see them with the `?`
|
||||
prefix, which prints the span along with the result. Moving to
|
||||
a more compact example format:
|
||||
```
|
||||
~tasfyn-partyv:dojo> ? 42
|
||||
@ud
|
||||
42
|
||||
~tasfyn-partyv:dojo> ? 0x2a
|
||||
@ux
|
||||
0x2a
|
||||
```
|
||||
`@ud` and `@ux` stand for "unsigned decimal" and "unsigned hex,"
|
||||
obviously. But what is this syntax?
|
||||
|
||||
We only derive spans through inference. So there's no language
|
||||
syntax for a span. We have to be able to print spans, though, if
|
||||
only for debugging and diagnostics. `@ud` is an print-only
|
||||
syntax. (In this case it happens to be the same as the `mold`
|
||||
syntax, but that's just a coincidence.)
|
||||
|
||||
## Looking at spans, part 2
|
||||
|
||||
A good way to teach yourself to think in nouns is to look not at
|
||||
the prettyprinted span, but at the actual noun it's made of.
|
||||
Since everything in Hoon is a noun, a span is a noun too. When
|
||||
we use `??` rather than `?` as a prefix, we see the noun:
|
||||
```
|
||||
~tasfyn-partyv:dojo> ?? 42
|
||||
[%atom %ud]
|
||||
42
|
||||
~tasfyn-partyv:dojo> ?? [42 0x2a]
|
||||
[%cell [%atom %ud] [%atom %ux]]
|
||||
[42 0x2a]
|
||||
```
|
||||
What is this `%atom` notation? Is it a real noun? Can anyone
|
||||
make one?
|
||||
```
|
||||
~tasfyn-partyv:dojo> %atom
|
||||
%atom
|
||||
~tasfyn-partyv:dojo> %foo
|
||||
%foo
|
||||
~tasfyn-partyv:dojo> [%foo %bar]
|
||||
[%foo %bar]
|
||||
```
|
||||
What if we look at the span?
|
||||
```
|
||||
~tasfyn-partyv:dojo> ? %foo
|
||||
%foo
|
||||
%foo
|
||||
~tasfyn-partyv:dojo> ?? %foo
|
||||
[%cube 7.303.014 %atom %tas]
|
||||
%foo
|
||||
```
|
||||
This takes a little bit of explaining. First of all, `7.303.014`
|
||||
is just the German (and Urbit) way of writing `7,303,014`, or the
|
||||
hexadecimal number `0x6f.6f66`, or the string "foo" as an
|
||||
unsigned integer. (It's much easier to work with large integers
|
||||
when the digits are grouped.) Second, remembering that cells
|
||||
nest right, `[%cube 7.303.014 %atom %tas]` is really `[%cube
|
||||
7.303.014 [%atom %tas]]`.
|
||||
|
||||
A `%cube` span is a constant -- a set of one noun, the atom
|
||||
`7.303.014`. But we still need to know how to print that noun.
|
||||
In this case, it's an `[%atom %tas]`, ie, a text symbol.
|
||||
|
||||
Cubes don't have to be symbols -- in fact, we can take the
|
||||
numbers we've just been using, and make them constants:
|
||||
```
|
||||
~tasfyn-partyv:dojo> %42
|
||||
%42
|
||||
~tasfyn-partyv:dojo> ? %42
|
||||
%42
|
||||
%42
|
||||
~tasfyn-partyv:dojo> ?? %42
|
||||
[%cube 42 %atom %ud]
|
||||
%42
|
||||
```
|
||||
|
||||
## Our first mold
|
||||
|
||||
After seeing a few span examples, are we ready to describe the
|
||||
set of all spans with a Hoon mold? Well, no, but let's try it
|
||||
anyway. Ignore the syntax (which we'll explain later; this is a
|
||||
tutorial, not a reference manual), and you'll get the idea:
|
||||
```
|
||||
++ span
|
||||
$% [%atom @tas]
|
||||
[%cell span span]
|
||||
[%cube * span]
|
||||
==
|
||||
```
|
||||
This mold is not the entire definition of `span`, just the cases
|
||||
we've seen so far. In English, a valid span is either:
|
||||
|
||||
- a cell with head `%atom`, and tail some symbol.
|
||||
- a cell with head `%cell`, and tail some pair of spans.
|
||||
- a cell with head `%cube`, and tail a noun-span pair.
|
||||
|
||||
The head of a span is essentially the tag in a variant record,
|
||||
a pattern every programming language has. To use the noun, we
|
||||
look at the head and then decide what to do with the tail.
|
269
pub/docs/dev/hoon/tutorial/0-nouns.mdy
Normal file
269
pub/docs/dev/hoon/tutorial/0-nouns.mdy
Normal file
@ -0,0 +1,269 @@
|
||||
---
|
||||
title: Hoon 101.0: nouns, spans, and molds
|
||||
sort: 0
|
||||
next: false
|
||||
---
|
||||
|
||||
# Hoon 101.0: nouns, spans and molds
|
||||
|
||||
Hoon is a strict, higher-order typed pure-functional language.
|
||||
|
||||
Why Hoon? Typed functional languages are known for a pleasant
|
||||
phenomenon: once your code compiles, it's quite likely to work.
|
||||
But most typed functional languages are conceptually dependent on
|
||||
abstract advanced math, and difficult to understand without it.
|
||||
|
||||
Hoon is a typed FP language for the common street programmer.
|
||||
Well-written Hoon is as concrete and data-oriented as possible.
|
||||
The less functional magic you use, the better. But the magic is
|
||||
there, mostly, if you need it.
|
||||
|
||||
The main disadvantage of Hoon is that its syntax and semantics
|
||||
are unfamiliar. The syntax will remind too many of Perl, but
|
||||
like most human languages (and unlike Perl) it combines a regular
|
||||
core structure with irregular variations. Its semantic
|
||||
complexity is bounded by the fact that the compiler is only 2000
|
||||
lines of Hoon (admittedly an expressive language). Most peoples'
|
||||
experience is that Hoon is much easier to learn than it looks.
|
||||
It does not look easy to learn, though!
|
||||
|
||||
But let's give it a try. One style point: we'll nest design
|
||||
digressions in braces. If you see a {paragraph} or two,
|
||||
assume it's of interest to language nerds only. These
|
||||
digressions are "guaranteed not on the test."
|
||||
|
||||
{The name "Hoon" is from the Wallace Stevens poem, _Tea at the
|
||||
Palaz of Hoon_. It also means "hooligan" in Australian.}
|
||||
|
||||
## Nouns: data made boring
|
||||
|
||||
A noun is an atom or a cell. An atom is any unsigned integer. A
|
||||
cell is an ordered pair of nouns.
|
||||
|
||||
The noun is an intentionally boring data model. Nouns don't have
|
||||
cycles (although a noun implementation should take advantage of
|
||||
acyclic graph structure). Noun comparison is always by value
|
||||
(there is no way for the programmer to test pointer equality).
|
||||
Nouns are strict; there is no such thing as an infinite noun.
|
||||
And, of course, nouns are immutable. There's basically no way to
|
||||
have any real fun with nouns.
|
||||
|
||||
{Nouns are Lisp's S-expressions, minus a lot of hacks, tricks,
|
||||
and features that made sense 50 years ago. In particular,
|
||||
because atoms are not tagged (an atom can encode a string, for
|
||||
instance), nouns need a static type system. How do you print an
|
||||
atom if you don't know whether it's a string or a number?}
|
||||
|
||||
## A type system for nouns
|
||||
|
||||
One obstacle to learning Hoon is that it has two quite distinct
|
||||
concepts that might equally be called a "type." Worse, most
|
||||
other typed functional languages are mathy and share a basically
|
||||
mathematical concept of "type." Hoon does not have this concept
|
||||
at all. We can't avoid using the T-word occasionally, but it has
|
||||
no precise meaning in Hoon and can be extremely confusing.
|
||||
|
||||
Hoon's two kinds of "type" are `span` and `mold`. A span is both
|
||||
a constructively defined set of nouns, and a semantic convention
|
||||
for users in that set. A `mold` is a function whose range is
|
||||
some useful span. A mold is always idempotent (for any noun x,
|
||||
`f(x)` equals `f(f(x))`), and its domain is any noun.
|
||||
|
||||
One way to explain this is that while a span is what most
|
||||
languages call a "type," Hoon has no syntax for the programmer to
|
||||
define a span directly. Instead, we use inference to define it
|
||||
as the range of a mold function. This mold can also be used to
|
||||
validate or normalize untrusted, untyped data -- a common problem
|
||||
in modern programming, because networks.
|
||||
|
||||
Hoon's inference algorithm is dumber than the unification
|
||||
algorithms (Hindley-Milner) used in most typed functional
|
||||
languages. Hoon thinks only forward, not backward. Eg, Haskell
|
||||
can infer the result type of a function from its argument
|
||||
(forward), or the argument type from the result (backward).
|
||||
Hoon can do the first but not the second.
|
||||
|
||||
So Hoon needs more manual typecasts, which you usually want
|
||||
anyway for prosaic software-engineering reasons. Otherwise its
|
||||
typesystem solves more or less the same job, including
|
||||
pattern-matching, genericity / typeclasses, etc.
|
||||
|
||||
{Sending a noun over the network is a good example. In a normal
|
||||
modern language, you serialize and deserialize a data type by
|
||||
extending your type to implement a serialization interface. In
|
||||
Hoon, any value is just a noun, so we have one function (`jam`)
|
||||
that converts any noun to an atom, and another (`cue`) that is
|
||||
its inverse. To validate, the receiver runs its own mold on the
|
||||
cued noun, and we've sent typed data over the network without any
|
||||
attack surface (except `jam` and `cue`, which fit on a page). No
|
||||
custom serialization methods are required, and the mold itself is
|
||||
never sent; protocol agreement is out of band.}
|
||||
|
||||
## Let's make some nouns
|
||||
|
||||
Nouns aren't even slightly hard. Let's make a noun:
|
||||
```
|
||||
~tasfyn-partyv:dojo> 42
|
||||
```
|
||||
You'll see the expression you entered, then the resulting value:
|
||||
```
|
||||
> 42
|
||||
42
|
||||
```
|
||||
Let's try a different value:
|
||||
```
|
||||
~tasfyn-partyv:dojo> 0x2a
|
||||
```
|
||||
You'll see:
|
||||
```
|
||||
> 0x2a
|
||||
0x2a
|
||||
```
|
||||
`42` and `0x2a` are actually *the same noun*, because they're the
|
||||
same number. But we don't just have the noun to print - we have
|
||||
a `[span noun]` cell (sometimes called a `vase`).
|
||||
|
||||
As you recall, a span defines a set of nouns and a semantic
|
||||
interpretation. As sets, both spans here are "any number". But
|
||||
semantically, `42` has a decimal span and `0x2a` hexadecimal, so
|
||||
they print differently.
|
||||
|
||||
{It's important to note that Hoon is a statically typed language.
|
||||
We don't work with vases unless we're dynamically compiling code,
|
||||
which is of course what we're doing here in the dojo. In Hoon,
|
||||
dynamic type is static type plus runtime compilation.}
|
||||
|
||||
Let's make some cells. Try these on your own urbit:
|
||||
```
|
||||
~tasfyn-partyv:dojo> [42 0x2a]
|
||||
~tasfyn-partyv:dojo> [42 [0x2a 420]]
|
||||
~tasfyn-partyv:dojo> [42 0x2a 420]
|
||||
```
|
||||
We observe that cells associate right: `[a b c]` is just another
|
||||
way of writing `[a [b c]]`.
|
||||
|
||||
{Lisp veterans beware: Hoon `[a b]` is Lisp `(a . b)`, Lisp
|
||||
`(a b)` is Hoon `[a b ~]`(`~` represents nil, with a value of
|
||||
atom `0`). Lisp and Hoon are both pair-oriented languages down
|
||||
below, but Lisp has a layer of sugar that makes it look
|
||||
list-oriented. Hoon loves its "improper lists," ie, tuples.}
|
||||
|
||||
## Looking at spans
|
||||
|
||||
What are these mysterious spans? We can see them with the `?`
|
||||
prefix, which prints the span along with the result. Moving to
|
||||
a more compact example format:
|
||||
```
|
||||
~tasfyn-partyv:dojo> ? 42
|
||||
@ud
|
||||
42
|
||||
~tasfyn-partyv:dojo> ? 0x2a
|
||||
@ux
|
||||
0x2a
|
||||
```
|
||||
`@ud` and `@ux` stand for "unsigned decimal" and "unsigned hex,"
|
||||
obviously.
|
||||
|
||||
{What is this span syntax? We only derive spans through
|
||||
inference. So there's no parsing grammar for a span. We have to
|
||||
be able to print spans, if only for debugging and diagnostics,
|
||||
but the syntax is output-only. As in this case, it often looks
|
||||
like the `mold` syntax, but the two are at opposite ends of the
|
||||
type food chain.}
|
||||
|
||||
## Looking at spans, part 2
|
||||
|
||||
Good style in Hoon is concrete style. When a Hoon programmer
|
||||
defines an abstract semantic value in terms of a noun, we rarely
|
||||
put a conceptual layer of abstraction between value and noun. We
|
||||
think of the semantic value as an interpretation of the
|
||||
concrete noun, and often we just think of the noun.
|
||||
|
||||
With the `?` command, we *do* use an abstract layer, by printing
|
||||
our span noun in a custom syntax. But we can also look at the
|
||||
noun directly, with the `??` command.
|
||||
|
||||
{Spans are an exception to the concrete principle, because we use
|
||||
"manual laziness" to define recursive structures. A recursive
|
||||
span contains Hoon code which is evaluated to apply it. In
|
||||
practice, it often contains the entire Urbit kernel, so you
|
||||
wouldn't want to try to print it in the dojo. If you find
|
||||
`??` taking a weirdly long time, this may have happened; just
|
||||
press ^C.}
|
||||
```
|
||||
~tasfyn-partyv:dojo> ?? 42
|
||||
[%atom %ud]
|
||||
42
|
||||
~tasfyn-partyv:dojo> ?? [42 0x2a]
|
||||
[%cell [%atom %ud] [%atom %ux]]
|
||||
[42 0x2a]
|
||||
```
|
||||
What is this `%atom` syntax? Is it a real noun? Can anyone
|
||||
make one?
|
||||
```
|
||||
~tasfyn-partyv:dojo> %atom
|
||||
%atom
|
||||
~tasfyn-partyv:dojo> %foo
|
||||
%foo
|
||||
~tasfyn-partyv:dojo> [%foo %bar]
|
||||
[%foo %bar]
|
||||
```
|
||||
What's the span of one of these symbols?
|
||||
```
|
||||
~tasfyn-partyv:dojo> ? %foo
|
||||
%foo
|
||||
%foo
|
||||
~tasfyn-partyv:dojo> ?? %foo
|
||||
[%cube 7.303.014 [%atom %tas]]
|
||||
%foo
|
||||
```
|
||||
This takes a little bit of explaining. `7.303.014` is just the
|
||||
Urbit (and German) way of writing the English number `7,303,014`,
|
||||
or the Urbit hex number `0x6f.6f66`, or the string "foo" as an
|
||||
unsigned integer with least-significant byte first.
|
||||
|
||||
A `%cube` span is a constant -- a set of one noun, the atom
|
||||
`7.303.014`. But we still need to know how to print that noun.
|
||||
In this case, it's an `[%atom %tas]`, ie, a text symbol.
|
||||
|
||||
Cubes don't have to be symbols -- in fact, we can take the
|
||||
numbers we've just been using, and make them constants:
|
||||
```
|
||||
~tasfyn-partyv:dojo> %42
|
||||
%42
|
||||
~tasfyn-partyv:dojo> ? %42
|
||||
%42
|
||||
%42
|
||||
~tasfyn-partyv:dojo> ?? %42
|
||||
[%cube 42 [%atom %ud]]
|
||||
%42
|
||||
```
|
||||
|
||||
## Our first mold
|
||||
|
||||
After seeing a few span examples, are we ready to describe the
|
||||
set of all spans with a Hoon mold? Well, no, but let's try it
|
||||
anyway. Ignore the syntax (which we'll explain later; this is a
|
||||
tutorial, not a reference manual), and you'll get the idea:
|
||||
```
|
||||
++ span
|
||||
$% [%atom p=@tas]
|
||||
[%cell p=span q=span]
|
||||
[%cube p=* q=span]
|
||||
==
|
||||
```
|
||||
This mold is not the entire definition of `span`, just the cases
|
||||
we've seen so far. In English, a valid span is either:
|
||||
|
||||
- a cell with head `%atom`, and tail some symbol.
|
||||
- a cell with head `%cell`, and tail some pair of spans.
|
||||
- a cell with head `%cube`, and tail a noun-span pair.
|
||||
|
||||
The head of a span is essentially the tag in a variant record,
|
||||
a pattern every programming language has. To use the noun, we
|
||||
look at the head and then decide what to do with the tail.
|
||||
|
||||
{A conventional naming strategy for simple, self-explaining
|
||||
structures is to name the legs of a tuple `p`, `q`, `r`, `s` and
|
||||
`t`. If you get all the way to `t`, your noun is probably not
|
||||
simple or self-explaining; meaningful names are recommended.}
|
@ -1,229 +0,0 @@
|
||||
# Hoon 1: twigs and legs
|
||||
|
||||
In the last chapter we learned how to make nouns. In this
|
||||
chapter we'll start programming a little.
|
||||
|
||||
## Nock for Hoon programmers
|
||||
|
||||
Hoon compiles itself to a pico-interpreter called Nock. This
|
||||
isn't the place to explain Nock (which is to Hoon much as
|
||||
assembly language is to C), but Nock is just a way to express a
|
||||
function as a noun.
|
||||
|
||||
Specifically, you can think of Nock as a (Turing-complete)
|
||||
interpreter shaped like (pseudocode):
|
||||
```
|
||||
Nock(subject formula) => product
|
||||
```
|
||||
Your function is the noun `formula`. The input to the function
|
||||
is the noun `subject`. The output is `product`. If something
|
||||
about this seems complicated or even interesting, you may be
|
||||
misunderstanding it.
|
||||
|
||||
## From Hoon to Nock
|
||||
|
||||
The Hoon parser turns an source expression (even one as simple as
|
||||
`42` from the last chapter) into a noun called a `twig`. If you
|
||||
know what an AST is, a twig is an AST. (If you don't know what
|
||||
an AST is, it's not worth the student loans.)
|
||||
|
||||
To simplify slightly, the Hoon compiler is shaped like:
|
||||
```
|
||||
Hoon(subject-span function-twig) => [product-span formula-nock]
|
||||
```
|
||||
Hoon, like Nock, is a *subject-oriented* language - your twig is
|
||||
always executed against one input noun, the subject. For any
|
||||
subject noun in `subject-span`, the compiler produces a Nock
|
||||
formula that computes `function-twig` on that subject, and a
|
||||
`product-span` that is the span of the product.
|
||||
|
||||
(Pretty much no other language works this way. In a normal
|
||||
language, your code is executed against a scope, stack, or other
|
||||
variable context, which may not even be a regular user-level
|
||||
value. This change is one of the hardest things to understand
|
||||
about Hoon, mostly because it's hard to stay convinced that
|
||||
subject-oriented programming is as straightforward as it is.)
|
||||
|
||||
## From constants to twigs
|
||||
|
||||
In the last chapter we were entering degenerate twigs like `42`.
|
||||
Obviously this doesn't use the subject at all.
|
||||
|
||||
Let's use the dojo variable facility (this is *not* Hoon syntax,
|
||||
just a dojo command) to make a test subject:
|
||||
```
|
||||
~tasfyn-partyv:dojo> =test [[[8 9] 5] [6 7]]
|
||||
```
|
||||
We can evaluate twigs against this subject with the Hoon `:`
|
||||
syntax (`a:b` uses the product of `b` as the subject of `a`).
|
||||
```
|
||||
~tasfyn-partyv:dojo> 42:test
|
||||
42
|
||||
```
|
||||
|
||||
## Tree addressing
|
||||
|
||||
The simplest twigs produce a subtree, or "leg", of the subject.
|
||||
A cell, of course, is a binary tree. The very simplest twig is
|
||||
`.`, which produces the root of the tree - the whole subject:
|
||||
```
|
||||
~tasfyn-partyv:dojo> .:test
|
||||
[[[8 9] 5] 6 7]
|
||||
```
|
||||
(If you're wondering why `[6 7]` got printed as `6 7`, remember
|
||||
that `[]` associates to the right.)
|
||||
|
||||
Hoon has a simple tree addressing scheme (inherited from Nock):
|
||||
the root is `1`, the head of `n` is `2n`, the tail is `2n+1`.
|
||||
The twig syntax is `+n`. Hence:
|
||||
```
|
||||
~tasfyn-partyv:dojo> +1:test
|
||||
[[[8 9] 5] 6 7]
|
||||
```
|
||||
Our example is a sort of Hoon joke, not very funny:
|
||||
```
|
||||
~tasfyn-partyv:dojo> +2:test
|
||||
[[8 9] 5]
|
||||
~tasfyn-partyv:dojo> +3:test
|
||||
[6 7]
|
||||
~tasfyn-partyv:dojo> +4:test
|
||||
[8 9]
|
||||
~tasfyn-partyv:dojo> +5:test
|
||||
5
|
||||
~tasfyn-partyv:dojo> +6:test
|
||||
6
|
||||
~tasfyn-partyv:dojo> +7:test
|
||||
7
|
||||
```
|
||||
And so on. An instinct for binary tree geometry develops over
|
||||
time as you use the system, rather the way most programmers
|
||||
learn to do binary math.
|
||||
|
||||
## Femur syntax
|
||||
|
||||
A "femur" is an alternative syntax for a tree address. The femur
|
||||
syntax creates a recognizable geometric shape by alternating
|
||||
between two head/tail pairs, read left to right: `-` and `+`,
|
||||
`<` and `>`.
|
||||
|
||||
Thus `-` is `+2`, `+` is `+3`, `+<` is `+6`, `->` is `+5`, `-<+`
|
||||
is `+9`, etc. The decimal numbers are distracting, whereas the
|
||||
glyph string binds directly to the tree geometry as you learn it.
|
||||
We actually almost never use the decimal tree geometry syntax.
|
||||
|
||||
## Simple faces
|
||||
|
||||
But it would be pretty tough to program in Hoon if explicit
|
||||
geometry was the only way of getting data out of a subject.
|
||||
Let's introduce some new syntax:
|
||||
```
|
||||
~tasfyn-partyv:dojo> foo=42
|
||||
foo=42
|
||||
~tasfyn-partyv:dojo> ? foo=42
|
||||
foo=@ud
|
||||
foo=42
|
||||
~tasfyn-partyv:dojo> ?? foo=42
|
||||
[%face %foo %atom %ud]
|
||||
foo=42
|
||||
```
|
||||
To extend our `++span` mold:
|
||||
```
|
||||
++ span
|
||||
$% [%atom @tas]
|
||||
[%cell span span]
|
||||
[%cube * span]
|
||||
[%face @tas span]
|
||||
==
|
||||
```
|
||||
The `%face` span wraps a label around a noun. Then we can
|
||||
get a leg by name. Let's make a new dojo variable:
|
||||
```
|
||||
~tasfyn-partyv:dojo> =test [[[8 9] 5] foo=[6 7]]
|
||||
```
|
||||
The syntax is what you might expect:
|
||||
```
|
||||
~tasfyn-partyv:dojo> foo:test
|
||||
[6 7]
|
||||
```
|
||||
Does this do what you expect it to do?
|
||||
```
|
||||
~tasfyn-partyv:dojo> +3:test
|
||||
foo=[6 7]
|
||||
~tasfyn-partyv:dojo> ? +3:test
|
||||
foo=[@ud @ud]
|
||||
foo=[6 7]
|
||||
~tasfyn-partyv:dojo> ?? +3:test
|
||||
[%face %foo %cell [%atom %ud] %atom %ud]
|
||||
foo=[6 7]
|
||||
```
|
||||
|
||||
## Interesting faces; wings
|
||||
|
||||
Again, you're probably used to name resolution in variable scopes
|
||||
and flat records, but not in trees. (Partly this is because the
|
||||
tradition in language design is to eschew semantics that make it
|
||||
hard to build simple symbol tables, because linear search of a
|
||||
big tree is a bad idea on '80s hardware.)
|
||||
|
||||
Let's look at a few more interesting face cases. First, suppose
|
||||
we have two cases of `foo`?
|
||||
```
|
||||
~tasfyn-partyv:dojo> =test [[foo=[8 9] 5] foo=[6 7]]
|
||||
~tasfyn-partyv:dojo> foo:test
|
||||
[8 9]
|
||||
```
|
||||
In the tree search, the head wins. We can overcome this with a
|
||||
`^` prefix, which tells the search to skip its first hit:
|
||||
```
|
||||
~tasfyn-partyv:dojo> =test [[foo=[8 9] 5] foo=[6 7]]
|
||||
~tasfyn-partyv:dojo> ^foo:test
|
||||
[6 7]
|
||||
```
|
||||
`^^foo` will skip two foos, `^^^foo` three, up to `n`.
|
||||
But what about nested labels?
|
||||
```
|
||||
~tasfyn-partyv:dojo> =test [[[8 9] 5] foo=[6 bar=7]]
|
||||
~tasfyn-partyv:dojo> bar:test
|
||||
/~tasfyn-partyv/home/~2015.9.16..21.40.21..1aec:<[1 1].[1 9]>
|
||||
-find-limb.bar
|
||||
find-none
|
||||
```
|
||||
It didn't seem to like that. We'll need a nested search:
|
||||
```
|
||||
~tasfyn-partyv:dojo> bar.foo:test
|
||||
7
|
||||
```
|
||||
`bar.foo` here is a `wing`, a search path in a noun. Note that
|
||||
the wing runs from left to right, ie, the opposite of most
|
||||
languages: `bar.foo` means "bar inside foo."
|
||||
|
||||
Each step in a wing is a `limb`. A limb can be a tree address,
|
||||
like `+3` or `.`, or a label like `foo`. We can combine them in
|
||||
one wing:
|
||||
```
|
||||
~tasfyn-partyv:dojo> bar.foo.+3:test
|
||||
7
|
||||
```
|
||||
|
||||
## Mutation
|
||||
|
||||
Well, not really. We can't modify nouns; the concept doesn't
|
||||
even make sense in Hoon. Rather, we build new nouns which are
|
||||
(logical -- the pointers are actually shared) copies of old ones,
|
||||
with changes.
|
||||
|
||||
Let's build a "mutated" copy of our test noun:
|
||||
```
|
||||
~tasfyn-partyv:dojo> test
|
||||
[[[8 9] 5] foo=[6 bar=7]]
|
||||
~tasfyn-partyv:dojo> test(foo 42)
|
||||
[[[8 9] 5] foo=42]
|
||||
~tasfyn-partyv:dojo> test(+8 %eight, bar.foo [%hello %world])
|
||||
[[[%eight 9] 5] foo=[6 [%hello %world]]]
|
||||
```
|
||||
As we see, there's no obvious need for the mutant noun to be
|
||||
shaped anything like the old noun. They're different nouns.
|
||||
|
||||
At this point, you have a simplified but basically sound idea of
|
||||
how Hoon builds and manages nouns. Next, it's time to do some
|
||||
programming.
|
281
pub/docs/dev/hoon/tutorial/1-twigs.mdy
Normal file
281
pub/docs/dev/hoon/tutorial/1-twigs.mdy
Normal file
@ -0,0 +1,281 @@
|
||||
---
|
||||
title: Hoon 101.1: twigs and legs
|
||||
sort: 1
|
||||
hide: true
|
||||
next: false
|
||||
---
|
||||
# Hoon 101.1: twigs and legs
|
||||
|
||||
In the last chapter we learned how to make nouns. In this
|
||||
chapter we'll start programming a little.
|
||||
|
||||
Reminder: we nest large digressions in curly braces. If you see
|
||||
a {paragraph} or two, assume it's of interest to language nerds
|
||||
only, and "guaranteed not on the test."
|
||||
|
||||
## Nock for Hoon programmers
|
||||
|
||||
Hoon compiles itself to a pico-interpreter called Nock, a
|
||||
combinator algebra defined in 200 words. This isn't the place to
|
||||
explain Nock (which is to Hoon much as assembly language is to
|
||||
C), but Nock is just a way to express a function as a noun.
|
||||
|
||||
Specifically, you can think of Nock as a (Turing-complete)
|
||||
interpreter shaped like (pseudocode):
|
||||
```
|
||||
Nock(problem) => product
|
||||
```
|
||||
This `problem` is always a cell `[subject formula]`. The
|
||||
function is `formula`. The input to the function is the noun
|
||||
`subject`. The output is the noun `product`.
|
||||
|
||||
(Why is Nock `[subject formula]` rather than `[formula subject]`?
|
||||
Or, to use more familiar terminology, why `[argument function]`
|
||||
rather than `[function argument]`? For no good reason, but it
|
||||
doesn't really matter in practice.)
|
||||
|
||||
## From Hoon to Nock
|
||||
|
||||
The Hoon parser turns an source expression (even one as simple as
|
||||
`42` from the last chapter) into a noun called a `twig`. If you
|
||||
know what an AST is, a twig is an AST. (If you don't know what
|
||||
an AST is, you probably don't have any student loans.)
|
||||
|
||||
To simplify slightly, the Hoon compiler is shaped like:
|
||||
```
|
||||
Hoon(subject-span function-twig) => [product-span formula-nock]
|
||||
```
|
||||
Hoon, like Nock, is a *subject-oriented* language. Your code is
|
||||
always executed against one input noun, the subject. For any
|
||||
subject noun in `subject-span` (ie, argument type), the compiler
|
||||
produces a Nock formula that computes `function-twig` on that
|
||||
subject, and a `product-span` that is the span of the product
|
||||
(ie, result type).
|
||||
|
||||
{This is really a nontrivial difference. In a normal,
|
||||
non-subject-oriented language, your code executes against a
|
||||
scope, stack, environment, or other variable context, probably
|
||||
not even a regular user-level value. For ordinary coders, "SOP"
|
||||
is one of the hardest things to understand about Hoon; for some
|
||||
reason, your brain keeps wanting the interpreter to be more
|
||||
complicated. There is of course a stack in a Nock interpreter,
|
||||
but solely for reduction state; actually, you can build a Nock
|
||||
that uses the C stack only, but still provides perfect TCO.}
|
||||
|
||||
## From constants to twigs
|
||||
|
||||
In the last chapter we were entering degenerate twigs like `42`.
|
||||
Obviously a numeric constant doesn't use the subject at all, so
|
||||
it's not a very interesting example of SOP.
|
||||
|
||||
Let's use the dojo variable facility (this is *not* Hoon syntax,
|
||||
just a dojo command) to make a test subject:
|
||||
```
|
||||
~tasfyn-partyv:dojo> =test [[[8 9] 5] [6 7]]
|
||||
```
|
||||
The `=test` command tells the dojo to rearrange its stock subject
|
||||
to include this `test` noun. Let's check that it's there:
|
||||
```
|
||||
~tasfyn-partyv:dojo> test
|
||||
[[[8 9] 5] 6 7]
|
||||
```
|
||||
{If you're wondering why `[6 7]` got printed as `6 7`, remember
|
||||
that `[]` associates to the right.}
|
||||
|
||||
We want to use `test`, this harmless little noun, as the subject
|
||||
for some equally harmless twigs. We can do this with the `:`
|
||||
syntax, which composes twigs in the functional sense. The twig
|
||||
`a:b` uses the product of twig `b` as the subject of twig `a`:
|
||||
```
|
||||
~tasfyn-partyv:dojo> 42:test
|
||||
42
|
||||
~tasfyn-partyv:dojo> 42:420
|
||||
42
|
||||
```
|
||||
|
||||
## Tree addressing
|
||||
|
||||
The simplest twigs produce a subtree, or "leg", of the subject.
|
||||
A cell, of course, is a binary tree. The very simplest twig is
|
||||
`.`, which produces the root of the tree - the whole subject:
|
||||
```
|
||||
~tasfyn-partyv:dojo> .:test
|
||||
[[[8 9] 5] 6 7]
|
||||
```
|
||||
Like human languages, Hoon is full of irregular abbreviations.
|
||||
The `.` syntax is a shorthand for `+1`:
|
||||
```
|
||||
~tasfyn-partyv:dojo> +1:test
|
||||
[[[8 9] 5] 6 7]
|
||||
```
|
||||
Hoon has a simple tree addressing scheme (inherited from Nock):
|
||||
the root is `1`, the head of `n` is `2n`, the tail is `2n+1`.
|
||||
The twig syntax for a tree address is `+n`.
|
||||
|
||||
In our example noun, each leaf is its own tree address:
|
||||
```
|
||||
~tasfyn-partyv:dojo> +2:test
|
||||
[[8 9] 5]
|
||||
~tasfyn-partyv:dojo> +3:test
|
||||
[6 7]
|
||||
~tasfyn-partyv:dojo> +4:test
|
||||
[8 9]
|
||||
~tasfyn-partyv:dojo> +5:test
|
||||
5
|
||||
~tasfyn-partyv:dojo> +6:test
|
||||
6
|
||||
~tasfyn-partyv:dojo> +7:test
|
||||
7
|
||||
```
|
||||
{An instinct for binary tree geometry develops over time as you
|
||||
use the system, rather the way most programmers learn to do
|
||||
binary math. No, really.}
|
||||
|
||||
## Lark syntax
|
||||
|
||||
This alternative syntax for a tree address maps noun geometry
|
||||
directly to a glyph, skipping numbers. Lark syntax creates a
|
||||
recognizable geometric shape by alternating between two head/tail
|
||||
pairs, read left to right: `-` and `+`, `<` and `>`.
|
||||
|
||||
Thus `-` is `+2`, `+` is `+3`, `+<` is `+6`, `->` is `+5`, `-<+`
|
||||
is `+9`, etc.
|
||||
|
||||
{Why lark syntax? Code full of numbers is ugly and distracting,
|
||||
and looks like hardcoded constants. We actually almost never use
|
||||
the `+` syntax.}
|
||||
|
||||
## Simple faces
|
||||
|
||||
Tree addressing is cool, but it would be pretty tough to program
|
||||
in Hoon if it was the only way of getting data out of a subject.
|
||||
|
||||
Let's introduce some new syntax:
|
||||
```
|
||||
~tasfyn-partyv:dojo> foo=42
|
||||
foo=42
|
||||
~tasfyn-partyv:dojo> ? foo=42
|
||||
foo=@ud
|
||||
foo=42
|
||||
~tasfyn-partyv:dojo> ?? foo=42
|
||||
[%face %foo [%atom %ud]]
|
||||
foo=42
|
||||
```
|
||||
To extend our `++span` mold:
|
||||
```
|
||||
++ span
|
||||
$% [%atom p=@tas]
|
||||
[%cell p=span p=span]
|
||||
[%cube p=* q=span]
|
||||
[%face p=@tas q=span]
|
||||
==
|
||||
```
|
||||
The `%face` span wraps a label around a noun. Then we can
|
||||
get a leg by name. Let's make a new dojo variable:
|
||||
```
|
||||
~tasfyn-partyv:dojo> =test [[[8 9] 5] foo=[6 7]]
|
||||
```
|
||||
The syntax is what you might expect:
|
||||
```
|
||||
~tasfyn-partyv:dojo> test
|
||||
[[[8 9] 5] foo=[6 7]]
|
||||
~tasfyn-partyv:dojo> foo:test
|
||||
[6 7]
|
||||
```
|
||||
Does this do what you expect it to do?
|
||||
```
|
||||
~tasfyn-partyv:dojo> +3:test
|
||||
foo=[6 7]
|
||||
~tasfyn-partyv:dojo> ? +3:test
|
||||
foo=[@ud @ud]
|
||||
foo=[6 7]
|
||||
~tasfyn-partyv:dojo> ?? +3:test
|
||||
[%face %foo [%cell [%atom %ud] [%atom %ud]]]
|
||||
foo=[6 7]
|
||||
```
|
||||
|
||||
## Interesting faces; wings
|
||||
|
||||
Again, you're probably used to name resolution in variable scopes
|
||||
and flat records, but not in trees. (Partly this is because the
|
||||
tradition in language design is to eschew semantics that make it
|
||||
hard to build simple symbol tables, because linear search of a
|
||||
nontrivial tree is a bad idea on '80s hardware.}
|
||||
|
||||
Let's look at a few more interesting face cases. First, suppose
|
||||
we have two cases of `foo`?
|
||||
```
|
||||
~tasfyn-partyv:dojo> =test [[foo=[8 9] 5] foo=[6 7]]
|
||||
~tasfyn-partyv:dojo> foo:test
|
||||
[8 9]
|
||||
```
|
||||
In the tree search, the head wins. We can overcome this with a
|
||||
`^` prefix, which tells the search to skip its first hit:
|
||||
```
|
||||
~tasfyn-partyv:dojo> ^foo:test
|
||||
[6 7]
|
||||
```
|
||||
`^^foo` will skip two foos, `^^^foo` three, *ad infinitum*.
|
||||
But what about nested labels?
|
||||
```
|
||||
~tasfyn-partyv:dojo> =test [[[8 9] 5] foo=[6 bar=7]]
|
||||
~tasfyn-partyv:dojo> bar:test
|
||||
/~tasfyn-partyv/home/~2015.9.16..21.40.21..1aec:<[1 1].[1 9]>
|
||||
-find-limb.bar
|
||||
find-none
|
||||
```
|
||||
We can't search *through* a label. If we want to get our `bar`
|
||||
out, we need to search *into* it:
|
||||
```
|
||||
~tasfyn-partyv:dojo> bar.foo:test
|
||||
7
|
||||
```
|
||||
`bar.foo` is what we call a `wing`, a search path in a noun.
|
||||
Note that the wing runs from left to right, ie, the opposite of
|
||||
most languages: `bar.foo` means "bar within foo."
|
||||
|
||||
Each step in a wing is a `limb`. {Most languages use metaphors;
|
||||
Hoon abuses them.} A limb can be a tree address, like `+3` or
|
||||
`.`, or a label like `foo`. We can combine them in one wing:
|
||||
```
|
||||
~tasfyn-partyv:dojo> bar.foo.+3:test
|
||||
7
|
||||
```
|
||||
It's important to note the difference between `bar.foo:test`
|
||||
and `bar:foo:test`, even though they produce the same product:
|
||||
```
|
||||
~tasfyn-partyv:dojo> bar:foo:test
|
||||
7
|
||||
```
|
||||
`bar.foo` is one twig, which we run on the product of `test`.
|
||||
That's different from running `bar` on the product of `foo` on
|
||||
the product of `test`.
|
||||
|
||||
## Mutation
|
||||
|
||||
Mutation? Well, not really. We can't modify nouns; the concept
|
||||
doesn't even make sense in Hoon.
|
||||
|
||||
Rather, we build new nouns which are {logical -- the pointers are
|
||||
shared, of course} copies of old ones, but with mutations. Let's
|
||||
build a "mutated" copy of our test noun:
|
||||
```
|
||||
~tasfyn-partyv:dojo> test
|
||||
[[[8 9] 5] foo=[6 bar=7]]
|
||||
~tasfyn-partyv:dojo> test(foo 42)
|
||||
[[[8 9] 5] foo=42]
|
||||
~tasfyn-partyv:dojo> test(+8 %eight, bar.foo [%hello %world])
|
||||
[[[%eight 9] 5] foo=[6 [%hello %world]]]
|
||||
```
|
||||
As we see, there's no need for the mutant noun to be shaped
|
||||
anything like the old noun. They're different nouns.
|
||||
|
||||
A mutation, like `+8 %eight`, specifies a wing and a twig.
|
||||
The wing, like `+8` or `bar.foo`, defines a leg to replace.
|
||||
The twig runs against the original subject.
|
||||
|
||||
Can we use mutation to build a cyclical noun? Nice try, but no:
|
||||
```
|
||||
~tasfyn-partyv:dojo> test(+8 test)
|
||||
[[[[[[8 9] 5] foo=[6 bar=7]] 9] 5] foo=[6 bar=7]]
|
||||
```
|
@ -1,3 +1,9 @@
|
||||
---
|
||||
title: Hoon 101.2: serious syntax
|
||||
sort: 2
|
||||
hide: true
|
||||
next: false
|
||||
---
|
||||
# Hoon 2: serious syntax
|
||||
|
||||
We've done a bunch of fun stuff on the command line. We know our
|
@ -1,4 +1,10 @@
|
||||
# Hoon 3: our first program
|
||||
---
|
||||
title: Hoon 101.3: our first program
|
||||
sort: 3
|
||||
hide: true
|
||||
next: false
|
||||
---
|
||||
# Hoon 101.3: our first program
|
||||
|
||||
It's time for us to do some actual programming. In this section,
|
||||
we'll work through that classic Urbit pons asinorum, decrement.
|
@ -1,3 +1,9 @@
|
||||
---
|
||||
title: Hoon 101.4: functions
|
||||
sort: 4
|
||||
hide: true
|
||||
next: false
|
||||
---
|
||||
# Hoon 4: toward actual functions
|
||||
|
||||
Okay, we've programmed. We've achieved decrement. We've written
|
Loading…
Reference in New Issue
Block a user