mirror of
https://github.com/urbit/shrub.git
synced 2024-12-14 20:02:51 +03:00
Merge branch 'master' into test
This commit is contained in:
commit
95bc3ba994
@ -21,7 +21,7 @@
|
||||
?~ +.arg -
|
||||
(fun.q.q tic.arg)
|
||||
%+ sole-lo
|
||||
[%& %helm-begin "your ship: ~"]
|
||||
[%& %helm-begin "your urbit: ~"]
|
||||
%+ sole-go fed:ag
|
||||
|= his=@p
|
||||
%+ sole-lo
|
||||
|
13
gen/hood/cancel.hoon
Normal file
13
gen/hood/cancel.hoon
Normal file
@ -0,0 +1,13 @@
|
||||
::
|
||||
:::: /hoon/cancel/hood/gen
|
||||
::
|
||||
/? 314
|
||||
::
|
||||
::::
|
||||
!:
|
||||
:- %say
|
||||
|= $: [now=@da eny=@uvI bec=beak]
|
||||
[[syd=@tas ~] ~]
|
||||
==
|
||||
:- %kiln-cancel
|
||||
syd
|
605
lib/base.css
605
lib/base.css
@ -70,552 +70,113 @@
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
body,
|
||||
html {
|
||||
font-family: "bau", "Helvetica Neue", helvetica, arial, sans-serif;
|
||||
@font-face {
|
||||
font-family: "scp";
|
||||
src: url("//storage.googleapis.com/urbit-extra/scp-bold.woff");
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
}
|
||||
code,
|
||||
pre,
|
||||
li:before,
|
||||
.spin,
|
||||
#bred a,
|
||||
h3.time {
|
||||
font-family: "scp", "Courier New", courier, monospace;
|
||||
@font-face {
|
||||
font-family: "scp";
|
||||
src: url("//storage.googleapis.com/urbit-extra/scp-black.woff");
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
}
|
||||
body,
|
||||
html {
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
line-height: 1.6rem;
|
||||
-webkit-text-size-adjust: none;
|
||||
}
|
||||
a {
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
border-bottom: 2px solid #000;
|
||||
display: inline-block;
|
||||
line-height: 0.8rem;
|
||||
}
|
||||
hr {
|
||||
display: inline-block;
|
||||
width: 6rem;
|
||||
border: 0;
|
||||
border-top: 2px solid #f4f4f4;
|
||||
}
|
||||
h1 {
|
||||
margin-top: 4rem;
|
||||
}
|
||||
h2,
|
||||
h3 {
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
strong {
|
||||
font-weight: 500;
|
||||
}
|
||||
h4 {
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
h5 {
|
||||
font-style: italic;
|
||||
font-weight: 200;
|
||||
margin: 0;
|
||||
}
|
||||
h1 code,
|
||||
h2 code,
|
||||
h3 code {
|
||||
font-size: inherit;
|
||||
padding: 0.3rem;
|
||||
}
|
||||
pre,
|
||||
code {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
pre {
|
||||
background-color: #f5f5f5;
|
||||
padding: 0.3rem;
|
||||
margin-left: -0.3rem;
|
||||
}
|
||||
code {
|
||||
line-height: 1.2rem;
|
||||
background-color: #f4f4f4;
|
||||
margin-top: -0.05rem;
|
||||
padding: 0.2rem;
|
||||
display: inline-block;
|
||||
}
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
li:before {
|
||||
content: "+";
|
||||
padding-right: 0.3rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
|
||||
html,
|
||||
input,
|
||||
button,
|
||||
body {
|
||||
font-family: "bau";
|
||||
font-size: 18px;
|
||||
}
|
||||
#nav,
|
||||
#cont {
|
||||
|
||||
pre,
|
||||
code,
|
||||
.mono {
|
||||
font-family:"scp";
|
||||
}
|
||||
|
||||
#c {
|
||||
width: 32rem;
|
||||
margin-left: -16rem;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
}
|
||||
#cont {
|
||||
width: 42rem;
|
||||
margin-left: -21rem;
|
||||
background-color: #fff;
|
||||
z-index: 1;
|
||||
}
|
||||
#nav {
|
||||
position: fixed;
|
||||
top: 0rem;
|
||||
width: 57rem;
|
||||
padding-top: 1rem;
|
||||
z-index: 0;
|
||||
margin-left: -32rem;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
transition: opacity 1s ease-in-out;
|
||||
}
|
||||
#nav.moving {
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
}
|
||||
#nav:hover {
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
}
|
||||
#cont {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
margin-bottom: 9rem;
|
||||
}
|
||||
.loading {
|
||||
display: inline-block;
|
||||
}
|
||||
.spin {
|
||||
color: #fff;
|
||||
padding: 0.6rem;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.1rem;
|
||||
z-index: 3;
|
||||
}
|
||||
.loading > .spin {
|
||||
background-color: #555;
|
||||
}
|
||||
#body .loading > .spin {
|
||||
background-color: #000;
|
||||
}
|
||||
.spin.state-0:before {
|
||||
content: "\2599";
|
||||
}
|
||||
.spin.state-1:before {
|
||||
content: "\259B";
|
||||
}
|
||||
.spin.state-2:before {
|
||||
content: "\259C";
|
||||
}
|
||||
.spin.state-3:before {
|
||||
content: "\259F";
|
||||
}
|
||||
#load.load {
|
||||
display: inline-block;
|
||||
font-weight: 500;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(10,10,10,0.4);
|
||||
opacity: 1;
|
||||
transition: opacity 1s ease-in-out;
|
||||
z-index: 4;
|
||||
}
|
||||
img.logo {
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
}
|
||||
h3.time {
|
||||
margin-top: 0.3rem;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 200;
|
||||
}
|
||||
#nav .links > div {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
#nav #sibs {
|
||||
width: 8rem;
|
||||
transition: margin-top 0.3s ease-in-out;
|
||||
overflow: hidden;
|
||||
}
|
||||
#nav #sibs > div {
|
||||
height: 20px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.focus #sibs {
|
||||
margin-top: 0 !important;
|
||||
transition: margin-top 0.3s ease-in-out;
|
||||
}
|
||||
#nav a,
|
||||
.list > li > a {
|
||||
text-transform: uppercase;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 200;
|
||||
letter-spacing: 1px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.list > li > a {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
#nav a,
|
||||
.list > li > a h1 {
|
||||
border-bottom: 1px solid #000;
|
||||
margin-right: 0.3rem;
|
||||
}
|
||||
#nav .active a {
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
}
|
||||
#up {
|
||||
padding-right: 1rem;
|
||||
margin-top: -0.3rem;
|
||||
}
|
||||
#sides {
|
||||
float: right;
|
||||
}
|
||||
#sides a {
|
||||
margin-right: 0.6rem;
|
||||
}
|
||||
#nav .arow-up,
|
||||
#nav .arow-next,
|
||||
#nav .arow-prev {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 0.4rem solid transparent;
|
||||
}
|
||||
#nav .arow-up {
|
||||
border-bottom: 0.6rem solid #000;
|
||||
}
|
||||
#nav .arow-next {
|
||||
border-left: 0.6rem solid #000;
|
||||
}
|
||||
#nav .arow-prev {
|
||||
border-right: 0.6rem solid #000;
|
||||
}
|
||||
#bred {
|
||||
width: 5rem;
|
||||
padding-right: 1rem;
|
||||
text-align: right;
|
||||
font-size: 0.6rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
#bred a {
|
||||
text-transform: lowercase;
|
||||
vertical-align: top;
|
||||
}
|
||||
#bred > div {
|
||||
float: right;
|
||||
}
|
||||
#bred > div > div {
|
||||
display: inline-block;
|
||||
margin-top: -0.2rem;
|
||||
}
|
||||
#bred a,
|
||||
#kids a {
|
||||
margin-right: 0.3rem;
|
||||
}
|
||||
#bred a {
|
||||
margin-left: 0.3rem;
|
||||
}
|
||||
.short {
|
||||
width: 32rem;
|
||||
}
|
||||
.list h1,
|
||||
.list li a > div p {
|
||||
margin: 0;
|
||||
}
|
||||
.list li a > div,
|
||||
.list li a > div p {
|
||||
display: inline;
|
||||
}
|
||||
.list li a > div p {
|
||||
margin-left: 0.3rem;
|
||||
}
|
||||
.list li a > div p:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
.list li a > div p code {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 400;
|
||||
text-transform: none;
|
||||
}
|
||||
.list li a h1 code {
|
||||
text-transform: lowercase;
|
||||
border-bottom: 1px solid #000;
|
||||
}
|
||||
.list li a code {
|
||||
padding: 0.2rem;
|
||||
}
|
||||
.list h1,
|
||||
.list li a > div div {
|
||||
display: inline;
|
||||
}
|
||||
.list li a > div div {
|
||||
margin-left: 0.6rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
.list h1 {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
.list.posts .post {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.list.posts .post h1 {
|
||||
text-transform: none;
|
||||
|
||||
h1 {
|
||||
font-size: 1.6rem;
|
||||
line-height: 1.8rem;
|
||||
margin-bottom: 1rem;
|
||||
display: block;
|
||||
}
|
||||
.list.posts .post h2 {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 400;
|
||||
line-height: 1rem;
|
||||
margin-top: 0;
|
||||
}
|
||||
.list.posts li.post:before {
|
||||
content: "";
|
||||
}
|
||||
div.root h1 {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
div.root .list .sub {
|
||||
margin-left: 0;
|
||||
margin-right: 0.6rem;
|
||||
}
|
||||
div.root > p {
|
||||
width: 27rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
h2.sub {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 400;
|
||||
line-height: 1rem;
|
||||
letter-spacing: 1px;
|
||||
margin-top: 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
div.post h1 {
|
||||
font-size: 2.8rem;
|
||||
line-height: 4rem;
|
||||
display: block;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
div.post h2 {
|
||||
line-height: 1rem;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
div.post h2 {
|
||||
margin-top: 4rem;
|
||||
}
|
||||
div.post h2 {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
div.post p {
|
||||
font-size: 1.2rem;
|
||||
line-height: 2.2rem;
|
||||
}
|
||||
div.post li p {
|
||||
display: inline;
|
||||
}
|
||||
div.toc {
|
||||
margin-top: 3rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
div.toc h1,
|
||||
div.toc h2,
|
||||
div.toc h3,
|
||||
div.toc h4 {
|
||||
font-weight: 400;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
font-size: 1.2rem;
|
||||
margin-top: 0.3rem;
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
div.toc h2 {
|
||||
|
||||
h1:after {
|
||||
content: "\2014";
|
||||
margin-left: 1rem;
|
||||
}
|
||||
div.toc h3 {
|
||||
margin-left: 2rem;
|
||||
|
||||
#c pre {
|
||||
font-size: .6rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
div.toc h4 {
|
||||
margin-left: 3rem;
|
||||
|
||||
#pass {
|
||||
width: 32rem;
|
||||
}
|
||||
div.toc h1.t {
|
||||
font-weight: 500;
|
||||
font-size: 2rem;
|
||||
text-decoration: none;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
#body .CodeMirror {
|
||||
font-size: 0.8rem;
|
||||
line-height: 1rem;
|
||||
}
|
||||
#body .CodeMirror .cm-header {
|
||||
font-weight: 200;
|
||||
}
|
||||
#body .CodeMirror-gutters {
|
||||
|
||||
button {
|
||||
border: .3rem solid #000;
|
||||
background-color: #fff;
|
||||
padding-right: 1rem;
|
||||
margin-left: -1rem;
|
||||
}
|
||||
.error {
|
||||
color: #f91733;
|
||||
}
|
||||
.warning {
|
||||
background-color: #ff3537;
|
||||
padding: 1rem;
|
||||
width: 18rem;
|
||||
margin: 2rem 0;
|
||||
color: #fff;
|
||||
}
|
||||
.warning a {
|
||||
color: inherit;
|
||||
border-color: #fff;
|
||||
}
|
||||
.warning h1 {
|
||||
font-size: 1rem;
|
||||
padding: .3rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.warning h1,
|
||||
.warning p {
|
||||
margin: 0 0.3rem;
|
||||
|
||||
.sig {
|
||||
font-weight: 400;
|
||||
font-size: 2rem;
|
||||
display: inline;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.warning.w {
|
||||
width: auto;
|
||||
|
||||
span#ship {
|
||||
font-family: 'bau';
|
||||
font-weight: 400;
|
||||
font-size: 1.2rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .1rem;
|
||||
display: inline-block;
|
||||
min-width: 1rem;
|
||||
}
|
||||
@media only screen and (max-width: 1170px) {
|
||||
#nav,
|
||||
#nav > div,
|
||||
#nav.up,
|
||||
#nav.top,
|
||||
#nav > .focus {
|
||||
transform: translate3d(0, 0, 0);
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
}
|
||||
#nav {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
opacity: 1;
|
||||
width: 42rem;
|
||||
margin-left: -21rem;
|
||||
background-color: #fff;
|
||||
z-index: 2;
|
||||
}
|
||||
#nav.m-down,
|
||||
#nav.m-up {
|
||||
position: absolute;
|
||||
}
|
||||
#nav.m-down.m-fixed {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
}
|
||||
#nav > div {
|
||||
max-height: 1rem;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease-in-out;
|
||||
}
|
||||
#nav > .focus {
|
||||
max-height: 40rem;
|
||||
transition: max-height 0.3s ease-in-out;
|
||||
}
|
||||
#cont {
|
||||
top: 3rem;
|
||||
}
|
||||
|
||||
input {
|
||||
font-family: 'scp';
|
||||
display: inline;
|
||||
}
|
||||
@media only screen and (min-width: 320px) and (max-width: 1024px) {
|
||||
body,
|
||||
html {
|
||||
font-size: 21px;
|
||||
}
|
||||
#nav,
|
||||
#cont {
|
||||
width: 94%;
|
||||
padding-left: 3%;
|
||||
margin-left: 0;
|
||||
}
|
||||
#nav {
|
||||
position: fixed;
|
||||
padding-top: 0;
|
||||
opacity: 1;
|
||||
left: 0;
|
||||
background-color: #fff;
|
||||
z-index: 2;
|
||||
}
|
||||
#nav > div {
|
||||
max-height: 1.4rem;
|
||||
}
|
||||
#nav > div {
|
||||
padding-top: 0.6rem;
|
||||
}
|
||||
#nav #sibs {
|
||||
width: 18rem;
|
||||
}
|
||||
#nav #sibs > div {
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
#nav a {
|
||||
display: inline-block;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
#nav #sides {
|
||||
float: right;
|
||||
}
|
||||
#nav .arow-up,
|
||||
#nav .arow-next,
|
||||
#nav .arow-prev {
|
||||
margin-right: 0;
|
||||
border: 0.4rem solid transparent;
|
||||
}
|
||||
#nav .arow-up {
|
||||
border-bottom: 0.6rem solid #000;
|
||||
}
|
||||
#nav .arow-next {
|
||||
border-left: 0.6rem solid #000;
|
||||
}
|
||||
#nav .arow-prev {
|
||||
margin-right: 1rem;
|
||||
border-right: 0.6rem solid #000;
|
||||
}
|
||||
#cont {
|
||||
top: 3rem;
|
||||
left: 0;
|
||||
padding-bottom: 9rem;
|
||||
}
|
||||
#cont h1:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
.short {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
span#ship,
|
||||
input {
|
||||
border: none;
|
||||
padding: .3rem;
|
||||
outline: none;
|
||||
border-bottom: 3px solid #555;
|
||||
}
|
||||
|
||||
@media only screen and (min-device-width: 320px) and (max-device-width: 480px) {
|
||||
#nav > div {
|
||||
max-height: 1.6rem;
|
||||
#c {
|
||||
width: 16rem;
|
||||
margin-left: -8rem;
|
||||
}
|
||||
#nav a {
|
||||
font-size: 0.7rem;
|
||||
#pass {
|
||||
width: 16rem;
|
||||
}
|
||||
#nav #sibs > div {
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
input {
|
||||
-webkit-appearance: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
10
pub/doc.md
10
pub/doc.md
@ -1,7 +1,7 @@
|
||||
urbit
|
||||
=====
|
||||
Urbit Manual
|
||||
============
|
||||
|
||||
is a general-purpose computing stack designed to live in the cloud.
|
||||
Urbit is a general-purpose computing stack designed to live in the cloud.
|
||||
|
||||
<list dataPreview="true"></list>
|
||||
|
||||
@ -11,6 +11,4 @@ is a general-purpose computing stack designed to live in the cloud.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
If you're new to the system, take a look at some of the
|
||||
[guides](doc/guide) to get oriented. Come join us on `:talk` in the
|
||||
`/urbit-meta` channel to ask questions and get help.
|
||||
Come join us on `:talk` in the `/urbit-meta` channel to ask questions and get help.
|
||||
|
@ -1,3 +1,7 @@
|
||||
---
|
||||
sort: 2
|
||||
---
|
||||
|
||||
arvo
|
||||
====
|
||||
|
@ -1,8 +0,0 @@
|
||||
<div class="short">
|
||||
|
||||
`%ives`
|
||||
=======
|
||||
|
||||
Isn't finished yet.
|
||||
|
||||
</div>
|
@ -1,5 +0,0 @@
|
||||
Ives: Reference
|
||||
===============
|
||||
|
||||
Ives: Commentary
|
||||
================
|
@ -1,8 +0,0 @@
|
||||
<div class="short">
|
||||
|
||||
`%jael`
|
||||
=======
|
||||
|
||||
Isn't finished yet.
|
||||
|
||||
</div>
|
@ -1,5 +0,0 @@
|
||||
Jael: Reference
|
||||
===============
|
||||
|
||||
Jael: Commentary
|
||||
================
|
@ -1,8 +0,0 @@
|
||||
<div class="short">
|
||||
|
||||
`%kahn`
|
||||
=======
|
||||
|
||||
Isn't finished yet.
|
||||
|
||||
</div>
|
@ -1,5 +0,0 @@
|
||||
Kahn: Reference
|
||||
===============
|
||||
|
||||
Kahn: Commentary
|
||||
================
|
@ -1,8 +0,0 @@
|
||||
<div class="short">
|
||||
|
||||
`%lunt`
|
||||
=======
|
||||
|
||||
Isn't finished yet.
|
||||
|
||||
</div>
|
@ -1,5 +0,0 @@
|
||||
Lunt: Reference
|
||||
===============
|
||||
|
||||
Lunt: Commentary
|
||||
================
|
@ -1,3 +1,7 @@
|
||||
---
|
||||
sort: 1
|
||||
---
|
||||
|
||||
<div class="short">
|
||||
|
||||
hoon
|
68
pub/doc/hoon/reference/pronunciation.md
Normal file
68
pub/doc/hoon/reference/pronunciation.md
Normal file
@ -0,0 +1,68 @@
|
||||
Pronunciation
|
||||
=============
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
Hoon is a reserved-word-free
|
||||
language - any text in the program is part of the program.
|
||||
|
||||
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 easy to
|
||||
say, others not so much. So we've renamed them:
|
||||
|
||||
ace space
|
||||
bar |
|
||||
bas \
|
||||
buc $
|
||||
cab _
|
||||
cen %
|
||||
col :
|
||||
com ,
|
||||
doq "
|
||||
dot .
|
||||
fas /
|
||||
gal <
|
||||
gar >
|
||||
hax #
|
||||
hep -
|
||||
kel {
|
||||
ker }
|
||||
ket ^
|
||||
lus +
|
||||
pam &
|
||||
pat @
|
||||
pel (
|
||||
per )
|
||||
sel [
|
||||
sem ;
|
||||
ser ]
|
||||
sig ~
|
||||
soq '
|
||||
tar *
|
||||
tec `
|
||||
tis =
|
||||
wut ?
|
||||
zap !
|
||||
|
||||
Memorizing these names seems like a burden, but it actually makes
|
||||
communicating about hoon code a lot faster and easier. `bartis`
|
||||
sounds a lot better than 'bar equals'.
|
||||
|
||||
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.
|
||||
|
||||
There are a few runes with irregular special pronunciations:
|
||||
|
||||
-- hephep phep
|
||||
+- lushep slep
|
||||
++ luslus slus
|
||||
== tistis stet
|
||||
+< lusgal glus
|
||||
+> lusgar gras
|
||||
-< hepgal gelp
|
||||
-> hepgar garp
|
4
pub/doc/hoon/tutorial.md
Normal file
4
pub/doc/hoon/tutorial.md
Normal file
@ -0,0 +1,4 @@
|
||||
Tutorials
|
||||
=========
|
||||
|
||||
<list dataPreview="true" titlesOnly="true"></list>
|
232
pub/doc/hoon/tutorial/0-nouns.md
Normal file
232
pub/doc/hoon/tutorial/0-nouns.md
Normal file
@ -0,0 +1,232 @@
|
||||
# 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.
|
229
pub/doc/hoon/tutorial/1-twigs.md
Normal file
229
pub/doc/hoon/tutorial/1-twigs.md
Normal file
@ -0,0 +1,229 @@
|
||||
# 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.
|
392
pub/doc/hoon/tutorial/2-syntax.md
Normal file
392
pub/doc/hoon/tutorial/2-syntax.md
Normal file
@ -0,0 +1,392 @@
|
||||
# Hoon 2: serious syntax
|
||||
|
||||
We've done a bunch of fun stuff on the command line. We know our
|
||||
nouns. It's time to actually write some serious code -- in a
|
||||
real source file.
|
||||
|
||||
## Building a simple generator
|
||||
|
||||
In Urbit there's a variety of source file roles, distinguished by
|
||||
the magic paths they're loaded from: `/gen` for generators,
|
||||
`/ape` for appliances, `/fab` for renderers, etc.
|
||||
|
||||
We'll start with a generator, the simplest kind of Urbit program.
|
||||
|
||||
### Create a sandbox desk
|
||||
|
||||
A desk is the Urbit equivalent of a `git` branch. We're just
|
||||
playing around here and don't intend to soil our `%home` desk with
|
||||
test files, so let's make a sandbox:
|
||||
```
|
||||
|merge %sandbox our %home
|
||||
```
|
||||
### Mount the sandbox
|
||||
|
||||
Your Urbit pier is in `~/tasfyn-partyv`, or at least mine is.
|
||||
So we can get our code into Urbit, run the command
|
||||
```
|
||||
~tasfyn-partyv:dojo> |mount /=sandbox=/gen %gen
|
||||
```
|
||||
mounts the `/gen` folder from the `%sandbox` desk in your Unix
|
||||
directory `~/tasfyn-partyv/gen`. The mount is a two-way sync,
|
||||
like your Dropbox. When you edit a Unix file and save, your edit
|
||||
is automatically committed as a change to `%sandbox`.
|
||||
|
||||
### Execute from the sandbox
|
||||
|
||||
The `%sandbox` desk obviously is merged from `%home`, so it
|
||||
contains find all the default facilities you'd expect there.
|
||||
Bear in mind, we didn't set it to auto-update when `%home`
|
||||
is updated (that would be `|sync` instead of `|merge`).
|
||||
|
||||
So we're not roughing it when we set the dojo to load from
|
||||
`%sandbox`:
|
||||
```
|
||||
[switch to %home]
|
||||
```
|
||||
|
||||
### Write your builder
|
||||
|
||||
Let's build the simplest possible kind of generator, a builder.
|
||||
With your favorite Unix text editor (there are Hoon modes for vim
|
||||
and emacs), create the file `~/tasfyn-partyv/gen/test.hoon`.
|
||||
Edit it into this:
|
||||
```
|
||||
:- %say |= * :- %noun
|
||||
[%hello %world]
|
||||
```
|
||||
Get the spaces exactly right, please. Hoon is not in general a
|
||||
whitespace-sensitive language, but the difference between one
|
||||
space and two-or-more matters. And for the moment, think of
|
||||
```
|
||||
:- %say |= * :- %noun
|
||||
```
|
||||
as gibberish boilerplate at the start of a file, like `#include
|
||||
"stdio.h"` at the start of a C program. Any of our old Hoon
|
||||
constants would work in place of `[%hello %world`].
|
||||
|
||||
Now, run your builder:
|
||||
```
|
||||
~tasfyn-partyv:dojo/sandbox> +test
|
||||
[%hello %world]
|
||||
```
|
||||
Obviously this is your first Hoon *program* per se.
|
||||
|
||||
## Hoon syntax 101
|
||||
|
||||
But what's up with this syntax?
|
||||
|
||||
### A syntactic apology
|
||||
|
||||
The relationship between ASCII and human programming languages
|
||||
is like the relationship between the electric guitar and
|
||||
rock-and-roll. If it doesn't have a guitar, it's not rock.
|
||||
Some great rockers play three chords, like Johnny Ramone; some
|
||||
shred it up, like Jimmy Page.
|
||||
|
||||
The two major families of ASCII-shredding languages are Perl and
|
||||
the even more spectacular APL. (Using non-ASCII characters is
|
||||
just a fail, but APL successors like J fixed this.) No one
|
||||
has any right to rag on Larry Wall or Ken Iverson, but Hoon,
|
||||
though it shreds, shreds very differently.
|
||||
|
||||
The philosophical case for a "metalhead" language is threefold.
|
||||
One, human beings are much better at associating meaning with
|
||||
symbols than they think they are. Two, a programming language is
|
||||
a professional tool and not a plastic shovel for three-year-olds.
|
||||
|
||||
And three, the alternative to heavy metal is keywords. When you
|
||||
use a keyword language, not only are you forcing the programmer
|
||||
to tiptoe around a ridiculous maze of restricted words used and
|
||||
reserved, you're expressing your program through two translation
|
||||
steps: symbol->English and English->computation. When you shred,
|
||||
you are going direct: symbol->computation. Especially in a pure
|
||||
language, this creates a sense of "seeing the function" which no
|
||||
keyword language can quite duplicate.
|
||||
|
||||
But any metalhead language you don't yet know is line noise.
|
||||
Let's get you up to speed as fast as possible.
|
||||
|
||||
### A glyphic bestiary
|
||||
|
||||
A programming language needs to be not just read but said. But
|
||||
no one wants to say "ampersand." Therefore, we've taken the
|
||||
liberty of assigning three-letter names to all ASCII glyphs.
|
||||
|
||||
Some of these bindings are obvious and some aren't. You'll be
|
||||
genuinely surprised at how easy they are to remember:
|
||||
```
|
||||
ace [1 space] dot . pan ]
|
||||
bar | fas / pel )
|
||||
bis \ gap [>1 space, nl] pid }
|
||||
buc $ hax # ran >
|
||||
cab _ ket ^ rep '
|
||||
cen % lep ( sac ;
|
||||
col : lit < tar *
|
||||
com , lus + tec `
|
||||
das - mat @ tis =
|
||||
den " med & wut ?
|
||||
dip { nap [ zap !
|
||||
```
|
||||
It's fun to confuse people by using these outside Urbit. A few
|
||||
digraphs also have irregular sounds:
|
||||
```
|
||||
== stet
|
||||
-- shed
|
||||
++ slus
|
||||
-> dart
|
||||
-< dusk
|
||||
+> lark
|
||||
+< lush
|
||||
```
|
||||
|
||||
### The shape of a twig
|
||||
|
||||
A twig, of course, is a noun. As usual, the easiest way to
|
||||
explain both the syntax that compiles into that noun, and the
|
||||
semantic meaning of the noun, is the noun's physical structure.
|
||||
|
||||
#### Autocons
|
||||
|
||||
A twig is always a cell, and any cell of twigs is a twig
|
||||
producing a cell. As an homage to Lisp, we call this
|
||||
"autocons." Where you'd write `(cons a b)` in Lisp, you write
|
||||
`[a b]` in Hoon, and the shape of the twig follows.
|
||||
|
||||
The `???` prefix prints a twig as a noun instead of running it.
|
||||
Let's see autocons in action:
|
||||
```
|
||||
~tasfyn-partyv:dojo/sandbox> ??? 42
|
||||
[%dtzy %ud 42]
|
||||
~tasfyn-partyv:dojo/sandbox> ??? 0x2a
|
||||
[%dtzy %ux 42]
|
||||
~tasfyn-partyv:dojo/sandbox> ??? [42 0xa]
|
||||
[[%dtzy %ud 42] %dtzy %ux 42]
|
||||
```
|
||||
(As always, it may confuse *you* that this is the same noun as
|
||||
`[[%dtzy %ud 42] [%dtzy %ux 42]]`, but it doesn't confuse Hoon.)
|
||||
|
||||
#### The stem-bulb pattern
|
||||
|
||||
If the head of your twig is a cell, it's an autocons. If the
|
||||
head is an atom, it's an unpronounceable four-letter symbol like
|
||||
the `%dtzy` above.
|
||||
|
||||
This is the same pattern as we see in the `span` mold -- a
|
||||
variant record, essentially, in nouns. The head of one of these
|
||||
cells is called the "stem." The tail is the "bulb." The shape
|
||||
of the bulb is totally dependent on the value of the stem.
|
||||
|
||||
#### Runes and stems
|
||||
|
||||
A "rune" (a word intentionally chosen to annoy Go programmers) is
|
||||
a digraph - a sequence of two ASCII glyphs. If you know C, you
|
||||
know digraphs like `->` and `?:` and are used to reading them as
|
||||
single characters.
|
||||
|
||||
In Hoon you can *say* them as words: "dasran" and "wattis"
|
||||
respectively. In a metalhead language, if we had to say
|
||||
"minus greater-than" and "question-colon", we'd just die.
|
||||
|
||||
Most twig stems are made from runes, by concatenating the glyph
|
||||
names and removing the vowels. For example, the rune `=+`,
|
||||
pronounced "tislus," becomes the stem `%tsls`. (Note that in
|
||||
many noun implementations, this is a 31-bit direct value.)
|
||||
|
||||
(Some stems (like `%dtzy`) are not runes, simply because they
|
||||
don't have regular-form syntax and don't need to use precious
|
||||
ASCII real estate. They are otherwise no different.)
|
||||
|
||||
An important point to note about runes: they're organized. The
|
||||
first glyph in the rune defines a category. For instance, runes
|
||||
starting with `.` compute intrinsics; runes starting with `|`
|
||||
produce cores; etc.
|
||||
|
||||
Another important point about runes: they come in two flavors,
|
||||
"natural" (stems interpreted directly by the compiler) and
|
||||
"synthetic" (macros, essentially).
|
||||
|
||||
(Language food fight warning: one advantage of Hoon over Lisp is
|
||||
that all Hoon macros are inherently hygienic. Another advantage
|
||||
is that Hoon has no (user-level) macros. In Hoon terms, nobody
|
||||
gets to invent their own runes. A DSL is always and everywhere
|
||||
a write-only language. Hoon shreds its ASCII pretty hard, but
|
||||
the same squiggles mean the same things in everyone's code.)
|
||||
|
||||
#### Wide and tall regular forms
|
||||
|
||||
A good rune example is the simple rune `=+`, pronounced "tislus",
|
||||
which becomes the stem `%tsls`. A `%tsls` twig has the shape
|
||||
`[%tsls twig twig]`.
|
||||
|
||||
The very elegance of functional languages creates a visual
|
||||
problem that imperative languages lack. An imperative language
|
||||
has distinct statements (with side effects) and (usually pure)
|
||||
expressions; it's natural that in most well-formatted code,
|
||||
statements flow vertically down the screen, and expressions grow
|
||||
horizontally across this. This interplay creates a natural and
|
||||
relaxing shape on your screen.
|
||||
|
||||
In a functional language, there's no difference. The trivial
|
||||
functional syntax is Lisp's, which has two major problems. One:
|
||||
piles of expression terminators build up at the bottom of complex
|
||||
functions. Two: the natural shape of code is diagonal. The more
|
||||
complex a function, the more it wants to besiege the right
|
||||
margin. The children of a node have to start to the right of its
|
||||
parent, so the right margin bounds the tree depth.
|
||||
|
||||
Hoon does not completely solve these problems, but alleviates
|
||||
them. In Hoon, there are actually two regular syntax forms for
|
||||
most twig cases: "tall" and "wide" form. Tall twigs can contain
|
||||
wide twigs, but not vice versa, so the visual shape of a program
|
||||
is very like that of a statements-and-expressions language.
|
||||
|
||||
Also, in tall mode, most runes don't need terminators. Take
|
||||
`=+`, for example. Since the parser knows to expect exactly
|
||||
two twigs after the `=+` rune, it doesn't need any extra syntax
|
||||
to tell it that it's done.
|
||||
|
||||
Let's try a wide `=+` in the dojo:
|
||||
```
|
||||
~tasfyn-partyv:dojo/sandbox> =+(planet=%world [%hello planet])
|
||||
[%hello %world]
|
||||
```
|
||||
(`=+` seems to be some sort of variable declaration? Let's not
|
||||
worry about it right now. We're on syntax.)
|
||||
|
||||
The wide syntax for a `=+` twig, or any binary rune: `(`, the
|
||||
first subtwig, one space, the second subtwig, and `)`). To read
|
||||
this twig out loud, you'd say:
|
||||
```
|
||||
tislus lap planet is cen world ace nep cen hello ace planet pen
|
||||
pal
|
||||
```
|
||||
("tis" not in a rune gets contracted to "is".)
|
||||
|
||||
Let's try a tall `=+` in `test.hoon`:
|
||||
```
|
||||
:- %say |= * :- %noun
|
||||
=+ planet=%world
|
||||
[%hello planet]
|
||||
```
|
||||
The tall syntax for a `=+` twig, or any binary rune: the rune, at
|
||||
least two spaces or one newline, the first subtwig, at least two
|
||||
spaces or one newline, the second subtwig. Again, tall subtwigs
|
||||
can be tall or wide; wide subtwigs have to be wide.
|
||||
|
||||
(Note that our boilerplate line is a bunch of tall runes on one
|
||||
line, with two-space gaps. This is unusual but quite legal, and
|
||||
not to be confused with the actual wide form.)
|
||||
|
||||
To read this twig out loud, you'd say:
|
||||
```
|
||||
tislus gap planet is cen world gap nep cen hello ace planet pen
|
||||
```
|
||||
#### Layout conventions
|
||||
|
||||
Should you use wide twigs or tall twigs? When? How? What
|
||||
should your code look like? You're the artist. Except for the
|
||||
difference between one space (`ace`) and more space (`gap`), the
|
||||
parser doesn't care how you format your code. Hoon is not Go --
|
||||
there are no fixed rules for doing it right.
|
||||
|
||||
However, the universal convention is to keep lines under 80
|
||||
characters. Also, hard tab characters are illegal. And when in
|
||||
doubt, make your code look like the kernel code.
|
||||
|
||||
##### Backstep indentation
|
||||
|
||||
Note that the "variable declaration" concept of `=+` (which is no
|
||||
more a variable declaration than a Tasmanian tiger is a tiger)
|
||||
works perfectly here. Because `[%hello planet]` -- despite being
|
||||
a subtree of the the `=+` twig -- is at the same indent level.
|
||||
So our code flows down the screen, not down and to the right, and
|
||||
of course there are no superfluous terminators. It looks good,
|
||||
and creates fewer hard-to-find syntax errors than you'd think.
|
||||
|
||||
This is called "backstep" indentation. Another example, using a
|
||||
ternary rune that has a strange resemblance to C:
|
||||
```
|
||||
:- %say |= * :- %noun
|
||||
=+ planet=%world
|
||||
?: =(%world planet)
|
||||
[%hello planet]
|
||||
[%goodbye planet]
|
||||
```
|
||||
It's not always the case when backstepping that the largest
|
||||
subtwig is at the bottom and loses no margin, but it often is.
|
||||
And not all runes have tuple structure; some are n-ary, and use
|
||||
the `==` terminator (again, pronounced "stet"):
|
||||
```
|
||||
:- %say |= * :- %noun
|
||||
=+ planet=%world
|
||||
?+ planet
|
||||
[%unknown planet]
|
||||
%world [%hello planet]
|
||||
%ocean [%goodbye planet]
|
||||
==
|
||||
```
|
||||
So we occasionally lose right-margin as we descend a deep twig.
|
||||
But we can keep this lossage low with good layout design. The
|
||||
goal is to keep the heavy twigs on the right, and Hoon tries as
|
||||
hard as possible to help you with this.
|
||||
|
||||
For instance, `=+` ("tislus") is a binary rune: `=+(a b)`. In
|
||||
most cases of `=+` the heavy twig is `b`, but sometimes it's `a`.
|
||||
So we can use its friend the `=-` rune ("tisdas") to get the same
|
||||
semantics with the right shape: `=-(b a)`.
|
||||
|
||||
#### Irregular forms
|
||||
|
||||
There are more regular forms than we've shown above, but not a
|
||||
lot more. Hoon would be quite easy to learn if it was only its
|
||||
regular forms. It wouldn't be as easy to read or use, though.
|
||||
The learning curve is important, but not all-important.
|
||||
|
||||
Some stems (like the `%dtzy` constants above) obviously don't and
|
||||
can't have any kind of regular form (which is why `%dtzy` is not
|
||||
a real digraph rune). Many of the true runes have only regular
|
||||
forms. But some have irregular forms. Irregular forms are
|
||||
always wide, but there is no other constraint on their syntax.
|
||||
|
||||
We've already encountered one of the irregular forms: `foo=42`
|
||||
from the last chapter, and `planet=%world` here. Let's unpack
|
||||
this twig:
|
||||
```
|
||||
~tasfyn-partyv:dojo/sandbox> ?? %world
|
||||
[%cube 431.316.168.567 %atom %tas]
|
||||
%world
|
||||
|
||||
~tasfyn-partyv:dojo/sandbox> ??? %world
|
||||
[%dtzz %tas 431.316.168.567]
|
||||
```
|
||||
Clearly, `%dtzz` is one of our non-regulars. But we can wrap it
|
||||
with our irregular form:
|
||||
```
|
||||
~tasfyn-partyv:dojo/sandbox> ?? planet=%world
|
||||
[%face %planet [%cube 431.316.168.567 %atom %tas]]
|
||||
planet=%world
|
||||
|
||||
~tasfyn-partyv:dojo/sandbox> ??? planet=%world
|
||||
[%ktts %planet %dtzz %tas 431.316.168.567]
|
||||
```
|
||||
Since `%ktts` is "kettis", ie, `^=`, this has to be the irregular
|
||||
form of
|
||||
```
|
||||
~tasfyn-partyv:dojo/sandbox> ^=(planet %world)
|
||||
planet=world
|
||||
```
|
||||
So if we wrote our example without this irregular form, it'd be
|
||||
```
|
||||
:- %say |= * :- %noun
|
||||
=+ ^=(planet %world)
|
||||
[%hello planet]
|
||||
```
|
||||
Or with a gratuitous use of tall form:
|
||||
```
|
||||
:- %say |= * :- %noun
|
||||
=+ ^= planet %world
|
||||
[%hello planet]
|
||||
```
|
||||
Now you know how to read Hoon! For fun, try to pronounce more of
|
||||
the code on this page. Please don't laugh too hard at yourself.
|
316
pub/doc/hoon/tutorial/3-program.md
Normal file
316
pub/doc/hoon/tutorial/3-program.md
Normal file
@ -0,0 +1,316 @@
|
||||
# Hoon 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.
|
||||
|
||||
If you learned Nock before Hoon, you've already done decrement.
|
||||
If not, all you need to know is that the only arithmetic
|
||||
intrinsic in Nock is increment -- in Hoon, the unary `.+` rune.
|
||||
So an actual decrement function is required.
|
||||
|
||||
In chapter 3, we write a decrement builder: more or less the
|
||||
simplest nontrivial Urbit program. We should be able to run this
|
||||
example:
|
||||
```
|
||||
~tasfyn-partyv:dojo/sandbox> +test 42
|
||||
41
|
||||
```
|
||||
|
||||
## What's in that subject?
|
||||
|
||||
As we've seen, Hoon works by running a twig against a subject.
|
||||
We've been cheerfully running twigs through three chapters while
|
||||
avoiding the question: what's in the subject? To avoid the issue
|
||||
we've built a lot of constants, etc.
|
||||
|
||||
Of course your twig's subject comes from whoever runs it. There
|
||||
is no one true subject. Our twigs on the command line are not
|
||||
run against the same subject as our generator code, even though
|
||||
they are both run by the same `:dojo` appliance.
|
||||
|
||||
But the short answer is that both command-line and builder get
|
||||
*basically* the same subject: some ginormous noun containing all
|
||||
kinds of bells and whistles and slicers and dicers, including a
|
||||
kernel library which can needless to say decrement in its sleep.
|
||||
|
||||
As yet you have only faced human-sized nouns. We need not yet
|
||||
acquaint you with this mighty Yggdrasil, Mother of Trees. First
|
||||
we need to figure out what she could even be made of.
|
||||
|
||||
## Clearing the subject
|
||||
|
||||
We'll start by clearing the subject:
|
||||
```
|
||||
:- %say |= * :- %noun
|
||||
=> ~
|
||||
[%hello %world]
|
||||
```
|
||||
The `=>` rune ("tisran"), for `=>(p q)` executes `p` against
|
||||
the subject, then uses that product as the subject of `q`.
|
||||
|
||||
(We've already used an irregular form of `=>`, or to be more
|
||||
precise its mirror `=<` ("tislit"). In chapter 1, when we wrote
|
||||
`+3:test`, we meant `=>(test +3)` or `=<(+3 test)`.)
|
||||
|
||||
What is this `~`? It's Hoon `nil`, a zero atom with this span:
|
||||
```
|
||||
~tasfyn-partyv:dojo/sandbox> ?? ~
|
||||
[%cube 0 %atom %n]
|
||||
~
|
||||
```
|
||||
We use it for list terminators and the like. Obviously, since
|
||||
our old test code is just a constant, a null subject works fine:
|
||||
```
|
||||
~tasfyn-partyv:dojo/sandbox> +test
|
||||
[%hello %world]
|
||||
```
|
||||
|
||||
## Getting an argument
|
||||
|
||||
Obviously, if we want to write a decrement builder, we'll have to
|
||||
get an argument from the command line. This involves changing
|
||||
the `test.hoon` boilerplate a little:
|
||||
```
|
||||
:- %say |= [* [[arg=@ud ~] ~]] :- %noun
|
||||
=> arg=arg
|
||||
[%hello arg]
|
||||
|
||||
~tasfyn-partyv:dojo/sandbox> +test 42
|
||||
[%hello 42]
|
||||
```
|
||||
`=> arg=arg` looks a little odd. We wouldn't ordinarily do
|
||||
this. We're just replacing a very interesting subject that
|
||||
contains `arg` with a very boring one that contains only `arg`,
|
||||
for the same reason we cleared the subject with `~`.
|
||||
|
||||
In case there's any doubt about the subject (`.` is limb syntax
|
||||
for `+1`, ie, the whole noun):
|
||||
```
|
||||
:- %say |= [* [[arg=@ud ~] ~]] :- %noun
|
||||
=> arg=arg
|
||||
.
|
||||
|
||||
~tasfyn-partyv:dojo/sandbox> +test 42
|
||||
arg=42
|
||||
```
|
||||
|
||||
We can even write a trivial increment function using `.+`:
|
||||
```
|
||||
:- %say |= [* [[arg=@ud ~] ~]] :- %noun
|
||||
=> arg=arg
|
||||
+(arg)
|
||||
|
||||
~tasfyn-partyv:dojo/sandbox> +test 42
|
||||
43
|
||||
```
|
||||
Below we'll skip both boilerplate lines in our examples.
|
||||
|
||||
## A core is a code-data cell
|
||||
|
||||
But how do we actually, like, code? The algorithm for decrement
|
||||
is clear. We need to count up to 41. (How do we run useful
|
||||
programs on a computer with O(n) decrement? That's an
|
||||
implementation detail.)
|
||||
|
||||
We'll need another kind of noun: the *core*. Briefly, the core
|
||||
is always a cell `[battery payload]`. The payload is data, the
|
||||
battery is code -- one or more Nock formulas, to be exact.
|
||||
|
||||
Consider a simple core with a one-formula battery. Remember, we
|
||||
create Nock formulas by compiling a twig against a subject. The
|
||||
subject is dynamic data, but its span is static. What span do we
|
||||
give the compiler, and what noun do we give the formula?
|
||||
|
||||
A core formula always has the core as its subject. The formula
|
||||
is essentially a computed attribute on the payload. But if the
|
||||
subject was just the payload, the formula couldn't recurse.
|
||||
|
||||
Of course, there is no need to restrict ourselves to one computed
|
||||
attribute. We can just stick a bunch of formulas together and
|
||||
call them a battery. The source twigs in this core are called
|
||||
"arms," which have labels just like the faces we saw earlier.
|
||||
|
||||
Hoon overloads computed attributes (arms) and literal attributes
|
||||
(legs) in the same namespace. A label in a wing may refer to
|
||||
either. To extend the name-resolution tree search described in
|
||||
chapter 1, when searching a core, we look for a matching arm.
|
||||
If we find it we're done. If we don't, or if a `^` mark makes us
|
||||
skip, we search into the payload.
|
||||
|
||||
If a name resolves to a core arm, but it's not the last limb in the
|
||||
wing, the arm produces the core itself. Similarly, when the
|
||||
wing is not an access but a mutation, the arm refers to the core.
|
||||
|
||||
This demands an example: if `foo` produces some core `c`, and
|
||||
`bar` is an arm in that `c` (which may be `foo` itself, or some
|
||||
leg within `foo`), `bar.foo` runs the arm formula with `c` as the
|
||||
subject. You might think that `moo.bar.foo` would compute
|
||||
`bar.foo`, then search for `moo` within that result. Instead, it
|
||||
searches for `moo` within `c`. (You can get the other result
|
||||
with `moo:bar.foo`.)
|
||||
|
||||
Does this sound too tricky? It should - it's about the most
|
||||
complicated feature of Hoon. It's all downhill once you
|
||||
understand cores.
|
||||
|
||||
Let's again extend our `++span` mold:
|
||||
```
|
||||
++ span
|
||||
$% [%atom @tas]
|
||||
[%cell span span]
|
||||
[%core span (map ,@tas twig)]
|
||||
[%cube * span]
|
||||
[%face @tas span]
|
||||
==
|
||||
```
|
||||
This definition of `%core` is somewhat simplified from the
|
||||
reality, but basically conveys it. (Moreover, this version of
|
||||
`span` describes every kind of noun we build.) In our `%core` we
|
||||
see a payload span and a name-to-twig arm table, as expected.
|
||||
|
||||
Is a core an object? Not quite, because an arm is not a method.
|
||||
Methods in an OO language have arguments. Arms are functions
|
||||
only of the payload. (A method in Hoon is an arm that produces a
|
||||
gate, which is another core -- but we're getting too far ahead.)
|
||||
However, the battery does look a lot like a classic "vtable."
|
||||
|
||||
## Increment with a core
|
||||
|
||||
Let's increment with a core:
|
||||
```
|
||||
=< inc
|
||||
|%
|
||||
++ inc
|
||||
+(arg)
|
||||
--
|
||||
|
||||
~tasfyn-partyv:dojo/sandbox> +test 42
|
||||
43
|
||||
```
|
||||
What's going on? We used the `|%` rune ("barcen") to produce a
|
||||
core. (There are a lot of runes which create cores; they all
|
||||
start with `|`, and are basically macros that turn into `|%`.)
|
||||
|
||||
The payload of a core produced with `|%` is the subject with
|
||||
which `|%` is compiled. We might say that `|%` wraps a core
|
||||
around its subject. In this case, the subject of the `|%`,
|
||||
and thus payload, is our `arg=@ud` argument.
|
||||
|
||||
Then we used this core as the subject of the simple wing `inc`.
|
||||
(Remember that `=<(a b)` is just `=>(b a)`.)
|
||||
|
||||
We can actually print out a core. Take out the `=< inc`:
|
||||
```
|
||||
|%
|
||||
++ inc
|
||||
+(arg)
|
||||
--
|
||||
|
||||
~tasfyn-partyv:dojo/sandbox> +test 42
|
||||
!!!
|
||||
|
||||
~tasfyn-partyv:dojo/sandbox> ? +test 42
|
||||
!!!
|
||||
```
|
||||
Cores can be large and complex, and we obviously can't render all
|
||||
the data in them, either when printing a type or a value. At
|
||||
some point, you'll probably make the mistake of printing a big
|
||||
core, maybe even the whole kernel, as an untyped noun. Just
|
||||
press ^C.
|
||||
|
||||
## Adding a counter
|
||||
|
||||
To decrement, we need to count up to the argument. So we need a
|
||||
counter in our subject, because where else would it go? Let's
|
||||
change the subject to add a counter, `pre`:
|
||||
```
|
||||
=> [pre=0 .]
|
||||
=< inc
|
||||
|%
|
||||
++ inc
|
||||
+(arg)
|
||||
--
|
||||
|
||||
~tasfyn-partyv:dojo/sandbox> +test 42
|
||||
43
|
||||
```
|
||||
Once again, `.` is the whole subject, so we're wrapping it in a
|
||||
cell whose head is `pre=0`. Through the magic of labels, this
|
||||
doesn't change the way we use `arg`, even though it's one level
|
||||
deeper in the subject tree. Let's look at the subject again:
|
||||
```
|
||||
=> [pre=0 .]
|
||||
.
|
||||
|
||||
~tasfyn-partyv:dojo/sandbox> +test 42
|
||||
[pre=0 arg=42]
|
||||
~tasfyn-partyv:dojo/sandbox> ? +test 42
|
||||
[pre=@ud arg=@ud]
|
||||
[pre=0 arg=42]
|
||||
```
|
||||
There's actually a simpler way to write this. We've seen it
|
||||
already. It's not exactly a variable declaration:
|
||||
```
|
||||
=+ pre=0
|
||||
.
|
||||
|
||||
~tasfyn-partyv:dojo/sandbox> +test 42
|
||||
[pre=0 arg=42]
|
||||
```
|
||||
|
||||
## We actually decrement
|
||||
|
||||
Now we can write our actual decrement program:
|
||||
```
|
||||
=+ pre=0
|
||||
=< dec
|
||||
|%
|
||||
++ dec
|
||||
?: =(arg +(pre))
|
||||
pre
|
||||
dec(pre +(pre))
|
||||
--
|
||||
|
||||
~tasfyn-partyv:dojo/sandbox> +test 42
|
||||
41
|
||||
```
|
||||
`=(a b)` is an irregular form of `.=(a b)`, ie, "dottis" or the
|
||||
noun `[%dtts a b]`. Likewise, `+(a)` is `.+(a)`, ie, "dotlus"
|
||||
or `[%dtls a]`.
|
||||
|
||||
`?:` is a regular rune which does exactly what you think it does.
|
||||
Bear in mind, though, that in Hoon 0 (`&`, "rob") is true and 1
|
||||
(`|`, "bar") is false.
|
||||
|
||||
The real action is in `dec(pre +(pre))`. This is obviously an
|
||||
irregular form -- it's the same mutation form we saw before.
|
||||
Writing it out in full regular form:
|
||||
```
|
||||
=+ pre=0
|
||||
=< dec
|
||||
|%
|
||||
++ dec
|
||||
?: =(arg +(pre))
|
||||
pre
|
||||
%= dec
|
||||
pre +(pre)
|
||||
==
|
||||
--
|
||||
~tasfyn-partyv:dojo/sandbox> +test 42
|
||||
41
|
||||
```
|
||||
`%=`, "centis", is the rune which almost every use of a wing
|
||||
resolves to. It might be called "evaluate with changes."
|
||||
|
||||
When we evaluate with changes, we take a wing (`dec`) here and
|
||||
evaluate it as described above. Searching in the subject, which
|
||||
is of course our core, we find an arm called `dec` and run it.
|
||||
|
||||
The changes (replacing `pre` with `+(pre)`) are always applied
|
||||
relative to the core we landed on (or the leg we landed on).
|
||||
The change wing is relative to this target; the subject of the
|
||||
replacement (`+(pre)`) is the original subject.
|
||||
|
||||
So, in English, we compute the `dec` arm again, against a new
|
||||
core with a new payload that contains an incremented `pre`.
|
||||
And thus, we decrement. Doesn't seem so hard, does it?
|
261
pub/doc/hoon/tutorial/4-functions.md
Normal file
261
pub/doc/hoon/tutorial/4-functions.md
Normal file
@ -0,0 +1,261 @@
|
||||
# Hoon 4: toward actual functions
|
||||
|
||||
Okay, we've programmed. We've achieved decrement. We've written
|
||||
what is in some sense a loop. What next?
|
||||
|
||||
Well... we're still feeling vaguely disappointed. Because we're
|
||||
supposed to be doing *functional programming*. And we haven't
|
||||
yet written any *functions*.
|
||||
|
||||
After all, in Hoon we don't really write a command-line utility
|
||||
to decrement `42`. We write `(dec 42)`. You probably realize
|
||||
that on the inside, this is not the same thing as a function in a
|
||||
normal functional language. The Tasmanian tiger is not a tiger.
|
||||
On the other hand, it certainly *looks* like a function call.
|
||||
|
||||
So how do we write the function?
|
||||
|
||||
In this chapter, we'll modify `+test` to extend the subject so
|
||||
that we can write our result as `(dec arg)`. Or rather, `(duck
|
||||
arg)`, because we want to get out of training wheels and stop
|
||||
clearing the subject soon.
|
||||
|
||||
## Form of the solution
|
||||
|
||||
```
|
||||
=< :- %say |= [* [[arg=@ud ~] ~]] :- %noun
|
||||
(duck arg)
|
||||
!! :: some interesting core
|
||||
```
|
||||
`!!`, or "zapzap" or `[%zpzp ~]`, can go anywhere a twig can and
|
||||
always crashes. Because its span is the empty set (`%void`), it
|
||||
doesn't cause type inference problems.
|
||||
|
||||
In place of the `!!`, we'll put a core, effectively a library,
|
||||
that provides our new, improved decrement function `duck`. We'll
|
||||
then call it with the irregular form, `(duck arg)`, which looks
|
||||
like a function call but is in fact some mysterious macro.
|
||||
|
||||
## Some interesting core
|
||||
|
||||
Translated into imperative programming, what we did in chapter 3
|
||||
was more like computing a function of a global variable. Now,
|
||||
we have to actually pass an argument to a function.
|
||||
|
||||
Here's our first try:
|
||||
```
|
||||
=< :- %say |= [* [[arg=@ud ~] ~]] :- %noun
|
||||
=+ gat=duck
|
||||
=<(run gat(sam arg))
|
||||
=> ~
|
||||
|%
|
||||
++ duck
|
||||
=+ sam=0
|
||||
=+ pre=0
|
||||
|%
|
||||
++ run
|
||||
?: =(sam +(pre))
|
||||
pre
|
||||
run(pre +(pre))
|
||||
--
|
||||
--
|
||||
|
||||
~tasfyn-partyv:dojo/sandbox> +test 42
|
||||
41
|
||||
```
|
||||
We step back and contemplate our handiwork. Is it good? Well...
|
||||
it works. Reading programs written without syntactic sugar is
|
||||
about as fun as eating raw chocolate nibs.
|
||||
|
||||
What did we do? In the `duck` arm (we often write `++duck`, for
|
||||
obvious reasons) we produce a core whose payload is `[pre=0 num=0
|
||||
~]`, and whose battery contains `++run`.
|
||||
|
||||
In the result twig, we first use `++duck` to extend our subject
|
||||
with a core named `gat`. We then use `run` on that gate. Why do
|
||||
we need this `gat`? Why can't we just write `=<(run duck(sam
|
||||
arg))`?
|
||||
|
||||
Because the arm is computed *after* the mutation. But here we
|
||||
need the mutated *result* of `++duck`. Instead, what this code
|
||||
is doing is trying to mutate `sam` within the core that contains
|
||||
`++duck`. Where it doesn't exist, so your code won't compile.
|
||||
|
||||
And note that with `=<`, we've placed our library structurally
|
||||
between the original subject and the program we're writing,
|
||||
but lexically at the bottom with zero left margin. We also
|
||||
clear the subject to keep things simple.
|
||||
|
||||
## A more regular structure
|
||||
|
||||
It actually gets worse. To make this code look simpler, we need
|
||||
to make it more complex. While "function calls" actually fit
|
||||
quite well into the Hoon architecture, they're also a nontrivial
|
||||
synthetic construction. We'll build the desugared form the hard
|
||||
way, then show you where we put the sugar in.
|
||||
|
||||
The desugared canonical decrement:
|
||||
```
|
||||
=< :- %say |= [* [[arg=@ud ~] ~]] :- %noun
|
||||
=+ gat=duck
|
||||
=<(run gat(sam arg))
|
||||
=> ~
|
||||
|%
|
||||
++ duck
|
||||
=+ sam=0
|
||||
|%
|
||||
++ run
|
||||
=+ pre=0
|
||||
=< loop
|
||||
|%
|
||||
++ loop
|
||||
?: =(sam +(pre))
|
||||
pre
|
||||
loop(pre +(pre))
|
||||
--
|
||||
--
|
||||
--
|
||||
|
||||
~tasfyn-partyv:dojo/sandbox> +test 42
|
||||
41
|
||||
```
|
||||
Yuck. Okay, let's fix this.
|
||||
|
||||
## Art of the loop
|
||||
|
||||
First, look at our little `++loop`. It works just like our old
|
||||
`++run` loop. We notice that there's actually something nice
|
||||
about it: we don't use the symbol `loop` anywhere outside these 7
|
||||
lines of code. It's not exported at all.
|
||||
|
||||
Actually, the symbol `loop` name is useless and redundant.
|
||||
Making up names is one of the hard problems in computer science,
|
||||
so why solve it? For just this reason, Hoon has an *empty name*,
|
||||
which as a constant is a zero-length symbol (`%$` instead of
|
||||
`%foo`), and as a limb is the `buc` symbol (`$`). With `$`,
|
||||
our loop becomes:
|
||||
```
|
||||
=< $
|
||||
|%
|
||||
++ $
|
||||
?: =(sam +(pre))
|
||||
pre
|
||||
$(sam +(run))
|
||||
--
|
||||
```
|
||||
This may not seem like a huge improvement. It's not. But it's
|
||||
exactly equivalent to the synthetic rune `|-`, "bardas":
|
||||
```
|
||||
|- ?: =(sam +(pre))
|
||||
pre
|
||||
$(pre +(pre))
|
||||
```
|
||||
This is obviously the canonical Hoon loop. It leaves us with
|
||||
```
|
||||
=< :- %say |= [* [[arg=@ud ~] ~]] :- %noun
|
||||
=+ gat=duck
|
||||
=<(run gat(sam arg))
|
||||
=> ~
|
||||
|%
|
||||
++ duck
|
||||
=+ sam=0
|
||||
|%
|
||||
++ run
|
||||
=+ pre=0
|
||||
|- ?: =(sam +(pre))
|
||||
pre
|
||||
$(pre +(pre))
|
||||
--
|
||||
--
|
||||
|
||||
~tasfyn-partyv:dojo/sandbox> +test 42
|
||||
41
|
||||
```
|
||||
|
||||
## Is this a lambda?
|
||||
|
||||
Could we use `$` for `++run`? It certainly sounds like the same
|
||||
kind of thing as `++loop` -- just a word we invented to mean "do
|
||||
it." Should the programmer have to invent these kinds of words?
|
||||
```
|
||||
=< :- %say |= [* [[arg=@ud ~] ~]] :- %noun
|
||||
=+ gat=duck
|
||||
=<($ gat(sam arg))
|
||||
=> ~
|
||||
|%
|
||||
++ duck
|
||||
=| sam=@ud
|
||||
|%
|
||||
=+ pre=0
|
||||
++ $
|
||||
|- ?: =(sam +(pre))
|
||||
pre
|
||||
$(pre +(pre))
|
||||
--
|
||||
--
|
||||
|
||||
~tasfyn-partyv:dojo/sandbox> +test 42
|
||||
41
|
||||
```
|
||||
(Besides `run` to `$`, we changed `=+ sam=0` to `=| sam=@ud`.
|
||||
Let's just remember that there's some magic here. We'll come
|
||||
back and explain it later.)
|
||||
|
||||
This is still kind of ugly -- but it's exactly equivalent to
|
||||
```
|
||||
=< :- %say |= [* [[arg=@ud ~] ~]] :- %noun
|
||||
=+ gat=duck
|
||||
=<($ gat(sam arg))
|
||||
=> ~
|
||||
|%
|
||||
++ duck
|
||||
|= sam=@ud
|
||||
=+ pre=0
|
||||
|- ?: =(sam +(pre))
|
||||
pre
|
||||
$(pre +(pre))
|
||||
--
|
||||
|
||||
~tasfyn-partyv:dojo/sandbox> +test 42
|
||||
41
|
||||
```
|
||||
Doesn't that look like a function? Indeed, we're done with
|
||||
`++duck` -- that's what a Hoon decrement should look like.
|
||||
If you squint a little, `|=` ("bartis") might even be a strange,
|
||||
deformed lambda rune.
|
||||
|
||||
Since it's doing something simple, we might well even compress
|
||||
the whole body of the function into one wide-form line:
|
||||
```
|
||||
=+(pre=0 |-(?:(=(sam +(pre)) pre $(pre +(pre)))))
|
||||
```
|
||||
(According, of course, to taste -- this is a bit tight for some.)
|
||||
|
||||
## Gates and how to call them
|
||||
|
||||
Our call site remains a disaster, though. We'll need moar sugar.
|
||||
|
||||
But first, let's look at this lambda-thing we've made. What is
|
||||
the noun produced by `++duck`? Our term for it is a "gate," but
|
||||
nobody will hate you for saying "function." And while we "slam"
|
||||
our gates, you can feel free to just "call" them.
|
||||
|
||||
A gate is a core, of course, but a special kind of core. All
|
||||
cores are shaped like `[battery payload]`. A gate is shaped like
|
||||
`[formula [sample context]]`. A gate has one arm, `$`, so its
|
||||
battery is just a formula. To slam a gate, you replace its
|
||||
sample (`+6` or `+<`, "luslit" or "lust") with your own noun,
|
||||
and apply the formula to the mutated gate.
|
||||
|
||||
As we explained earlier, `duck(sam arg)` is not the right way to
|
||||
mutate the gate we make with `duck`, because it's actually
|
||||
trying to mutate the core we used to make `duck`. But there has
|
||||
to be some sugar to do this, and there is: `%*`, "centar". We
|
||||
can replace our call site with `%*($ duck sam arg)`.
|
||||
|
||||
This is also not quite orthodox, because the whole point of a
|
||||
gate is the canonical shape that defines a calling convention.
|
||||
We can and should say: `%*($ duck +< arg)`.
|
||||
|
||||
Unsurprisingly, this in turn is `%-(duck arg)` in regular form,
|
||||
or `(duck arg)`
|
@ -1,9 +1,13 @@
|
||||
---
|
||||
sort: 3
|
||||
---
|
||||
|
||||
<div class="short">
|
||||
|
||||
References
|
||||
Interpreter
|
||||
==========
|
||||
|
||||
These references cover terminology and our interpreter, `vere`.
|
||||
The urbit interpreter and C code.
|
||||
|
||||
</div>
|
||||
|
@ -1,3 +1,7 @@
|
||||
---
|
||||
sort: 0
|
||||
---
|
||||
|
||||
<div class="short">
|
||||
|
||||
nock
|
@ -1,14 +0,0 @@
|
||||
<div class="short">
|
||||
|
||||
talk
|
||||
====
|
||||
|
||||
talk is our messaging application.
|
||||
|
||||
talk is in dire need of better documentation
|
||||
|
||||
</div>
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
This document should include <a href="./talk/help">the command reference</a> somehow.
|
@ -1,24 +0,0 @@
|
||||
Besides `;help`, there are four main `talk` commands:
|
||||
|
||||
;join ~urbit-name/channel
|
||||
|
||||
`;join` subscribes your main feed to a remote channel.
|
||||
|
||||
;create channel %name 'description'
|
||||
|
||||
`;create` creates a channel on your urbit.
|
||||
|
||||
;<number>
|
||||
|
||||
`;<number>` activates a previous message number, like a URL that got
|
||||
clipped.
|
||||
|
||||
;<target>
|
||||
|
||||
`;<target>` sets the target for your messages, such as `;~urbit-name`
|
||||
for a private message, or `;/channel`
|
||||
|
||||
;
|
||||
|
||||
By itself is "autotarget", and maintains the audience of the last message
|
||||
heard.
|
10
pub/doc/tools.mdy
Normal file
10
pub/doc/tools.mdy
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
sort: 4
|
||||
---
|
||||
|
||||
tools
|
||||
====
|
||||
|
||||
User-level tools and utilities.
|
||||
|
||||
<list></list>
|
207
pub/doc/tools/clay.md
Normal file
207
pub/doc/tools/clay.md
Normal file
@ -0,0 +1,207 @@
|
||||
`%clay`
|
||||
========
|
||||
|
||||
## Paths
|
||||
|
||||
### Structure
|
||||
|
||||
Urbit paths have a very specific structure. First, since the clay
|
||||
filesystem has a global namespace, the first element in any path
|
||||
is the particular urbit whose filesystem you are trying to
|
||||
access.
|
||||
|
||||
The second element specifies which desk you wish to access on
|
||||
that urbit. Desks are independent branches (in the
|
||||
revision-control sense) of their filesystem.
|
||||
|
||||
The third element specifies the revision number for that
|
||||
desk. The remainder of the path is the path to the file.
|
||||
|
||||
Thus, a path in clay is:
|
||||
|
||||
`/urbit/desk/revision/path`.
|
||||
|
||||
For example, to get revision 5 of `/try/readme/md` off the `home`
|
||||
desk on `~sampel-sipnym`, use:
|
||||
|
||||
`/~sampel-sipnym/home/5/try/readme/md`.
|
||||
|
||||
### Shortcuts
|
||||
|
||||
`%` refers to the current working
|
||||
directory. `%%` refers to our parent, `%%%` refers to our
|
||||
grandparent, and so forth.
|
||||
|
||||
For example:
|
||||
|
||||
XX TBD
|
||||
|
||||
|
||||
From the other direction, inserting a `=` into a path copies the
|
||||
corresponding element from the current path into the path that
|
||||
you're trying to access.
|
||||
|
||||
For example, if the current path is referencing our ship at the
|
||||
current time, to reference `/try/readme`, use:
|
||||
|
||||
`/===try/readme`.
|
||||
|
||||
|
||||
### Accessing commits
|
||||
|
||||
There are three ways to refer to particular commits in the
|
||||
revision history. First, one can use the revision number.
|
||||
Second, one can use any absolute time between the one numbered
|
||||
commit and the next (inclusive of the first, exclusive of the
|
||||
second). Thirdly, every desk has a map of labels to revision
|
||||
numbers. These labels may be used to refer to specific commits.
|
||||
|
||||
|
||||
## `ls`
|
||||
|
||||
`+ls /path` gives a directory listing at a path
|
||||
|
||||
## `cat`
|
||||
|
||||
`+cat /path`
|
||||
prints out the file at the given path.
|
||||
|
||||
## `mount`
|
||||
|
||||
It's often useful to "mount" the clay filesystem to unix, so that
|
||||
you can interact with it with the traditional unix tools. The
|
||||
syntax to do this is as follows:
|
||||
|
||||
|mount /path [%mount-point]
|
||||
|
||||
This mirrors the desk out to unix in at the path
|
||||
`<pier-directory> <mount-point>`. If you don't supply a
|
||||
`%mount-point`, we use the last element in the path. Thus, if
|
||||
you mount `%/pub/doc`, it'll by default put it in `doc`.
|
||||
|
||||
*The mount point is monitored Dropbox-style, so every change you
|
||||
make to the file in unix is automatically commited to clay.*
|
||||
|
||||
You can unmount by specifying either the path or the mount point.
|
||||
|
||||
|unmount /path
|
||||
|unmount %mount-point
|
||||
|
||||
## `merge`
|
||||
|
||||
Often, it's useful to be able to merge a desk into another desk.
|
||||
The other desk does not, of course, need to be on the same urbit:
|
||||
for example, the standard way to distribute an app is to put it
|
||||
on a desk and let other people merge it into their own urbit.
|
||||
|
||||
The syntax is as follows:
|
||||
|
||||
|merge %to-desk ~from-urbit %from-desk [%strategy]
|
||||
|
||||
There are seven different merge strategies. Throughout our
|
||||
discussion, we'll say that the merge is from Alice's desk to
|
||||
Bob's.
|
||||
|
||||
### Native strategies
|
||||
|
||||
A `%init` merge should be used iff it's the first commit to a
|
||||
desk. The head of Alice's desk is used as the number 1 commit to
|
||||
Bob's desk. Obviously, the ancestry remains intact when
|
||||
traversing the parentage of the commit, even though previous
|
||||
commits are not numbered for Bob's desk.
|
||||
|
||||
A `%this` merge means to keep what's in Bob's desk, but join the
|
||||
ancestry. Thus, the new commit has the head of each desk as
|
||||
parents, but the data is exactly what's in Bob's desk. For those
|
||||
following along in git, this is the 'ours' merge strategy, not
|
||||
the '--ours' option to the 'recursive' merge strategy. In other
|
||||
words, even if Alice makes a change that does not conflict with
|
||||
Bob, we throw it away.
|
||||
|
||||
A `%that` merge means to take what's in Alice's desk, but join
|
||||
the ancestry. This is the reverse of `%this`.
|
||||
|
||||
A `%fine` merge is a "fast-forward" merge. This succeeds iff one
|
||||
head is in the ancestry of the other. In this case, we use the
|
||||
descendant as our new head.
|
||||
|
||||
For `%meet`, `%mate`, and `%meld` merges, we first find the most
|
||||
recent common ancestor to use as our merge base. If we have no
|
||||
common ancestors, then we fail. If we have multiple most
|
||||
recent common ancestors, then we have a criss-cross situation,
|
||||
which should be handled delicately. At present, we don't handle
|
||||
this kind of situation, but something akin to git's 'recursive'
|
||||
strategy should be implemented in the future.
|
||||
|
||||
There's a functional inclusion ordering on `%fine`, `%meet`,
|
||||
`%mate`, and `%meld` such that if an earlier strategy would have
|
||||
succeeded, then every later strategy will produce the same
|
||||
result. Put another way, every earlier strategy is the same as
|
||||
every later strategy except with a restricted domain.
|
||||
|
||||
A `%meet` merge only succeeds if the changes from the merge base
|
||||
to Alice's head (hereafter, "Alice's changes") are in different
|
||||
files than Bob's changes. In this case, the parents are both
|
||||
Alice's and Bob's heads, and the data is the merge base plus
|
||||
Alice's changed files plus Bob's changed files.
|
||||
|
||||
A `%mate` merge attempts to merge changes to the same file when
|
||||
both Alice and Bob change it. If the merge is clean, we use it;
|
||||
otherwise, we fail. A merge between different types of changes --
|
||||
for example, deleting a file vs changing it -- is always a
|
||||
conflict. If we succeed, the parents are both Alice's and Bob's
|
||||
heads, and the data is the merge base plus Alice's changed files
|
||||
plus Bob's changed files plus the merged files.
|
||||
|
||||
A `%meld` merge will succeed even if there are conflicts. If
|
||||
there are conflicts in a file, then we use the merge base's
|
||||
version of that file, and we produce a set of files with
|
||||
conflicts. The parents are both Alice's and Bob's heads, and the
|
||||
data is the merge base plus Alice's changed files plus Bob's
|
||||
changed files plus the successfully merged files plus the merge
|
||||
base's version of the conflicting files.
|
||||
|
||||
### Metastrategies
|
||||
|
||||
There's also a meta-strategy `%auto`, which is the most common.
|
||||
If no strategy is supplied, then `%auto` is assumed. `%auto`
|
||||
checks to see if Bob's desk exists, and if it doesn't we use a
|
||||
`%init` merge. Otherwise, we progressively try `%fine`,
|
||||
`%meet`, and `%mate` until one succeeds.
|
||||
|
||||
If none succeed, we merge Bob's desk into a scratch desk. Then,
|
||||
we merge Alice's desk into the scratch desk with the `%meld`
|
||||
option to force the merge. For each file in the produced set of
|
||||
conflicting files, we call the `++mash` function for the
|
||||
appropriate mark, which annotates the conflicts if we know how.
|
||||
|
||||
Finally, we display a message to the user informing them of the
|
||||
scratch desk's existence, which files have annotated conflicts,
|
||||
and which files have unannotated conflicts. When the user has
|
||||
resolved the conflicts, they can merge the scratch desk back into
|
||||
Bob's desk. This will be a `%fine` merge since Bob's head is in
|
||||
the ancestry of the scratch desk.
|
||||
|
||||
## Autosync
|
||||
|
||||
Since clay is reactive, it's possible for changes to the
|
||||
filesystem to cause various actions. An important use of this is
|
||||
in enabling "autosync". When a desk is synced to another, any
|
||||
changes to the first desk are automatically applied to the
|
||||
second.
|
||||
|
||||
This isn't simply mirroring, since the local desk might have
|
||||
changes of its own. We use the full merge capabilities of clay
|
||||
to try to make the merge clean. If there are conflicts, it'll
|
||||
notify you and let you resolve them.
|
||||
|
||||
There can be complex sync flows, some of which are useful.
|
||||
Often, many urbits will be synced to some upstream desk that is
|
||||
trusted to provide updates. Sometimes, it's useful to sync two
|
||||
desks to each other, so that changes to one or the other are
|
||||
mirrored.
|
||||
|
||||
The syntax for syncing and unsyncing desks is as follows:
|
||||
|
||||
|sync %to-desk ~from-urbit %from-desk
|
||||
|unsync %to-desk ~from-urbit %from-desk
|
548
pub/doc/tools/dojo.md
Normal file
548
pub/doc/tools/dojo.md
Normal file
@ -0,0 +1,548 @@
|
||||
|
||||
# `:dojo`
|
||||
|
||||
The dojo is a typed functional shell. Its prompt is:
|
||||
|
||||
~urbit-name:dojo>
|
||||
|
||||
|
||||
### Quickstart
|
||||
|
||||
To print a Hoon expression or other recipe:
|
||||
|
||||
~urbit-name:dojo> (add 2 2)
|
||||
|
||||
To save a recipe as a variable `foo`:
|
||||
|
||||
~urbit-name:dojo> =foo (add 2 2)
|
||||
|
||||
To save as a unix file (`$pier/.urb/put/foo/bar.baz`):
|
||||
|
||||
~urbit-name:dojo> .foo/bar/baz (add 2 2)
|
||||
|
||||
To save as an urbit file (`/===/foo/bar/baz`):
|
||||
|
||||
~urbit-name:dojo> *foo/bar/baz (add 2 2)
|
||||
|
||||
A noun generator with ordered and named arguments:
|
||||
|
||||
~urbit-name:dojo> +make one two three, =foo (add 2 2), =bar 42
|
||||
|
||||
A poke message to an urbit daemon:
|
||||
|
||||
~urbit-name:dojo> :~urbit-name/talk (add 2 2)
|
||||
|
||||
A system command to `:hood`:
|
||||
|
||||
~urbit-name:dojo> |reload %vane
|
||||
|
||||
### Manual
|
||||
|
||||
An Urbit value is called a "noun." A noun is either an unsigned
|
||||
integer ("atom") or an ordered pair of nouns ("cell"). Nouns
|
||||
are just values, with no cyclic structure or pointer identity.
|
||||
|
||||
The dojo is your safe space for hand-to-hand combat with nouns.
|
||||
Every dojo command builds a "product" noun functionally, then
|
||||
applies this product in a side effect -- show, save, or send.
|
||||
|
||||
#### Theory
|
||||
|
||||
The dojo is not just a Hoon interpreter. Hoon is a purely
|
||||
functional language; dojo recipes are *conceptually* functional,
|
||||
but they often use concrete actions or interactions. A simple
|
||||
Hoon expression is only one kind of recipe.
|
||||
|
||||
A recipe can get data from an HTTP GET request or an interactive
|
||||
input dialog. It can also query, even block on, the Urbit
|
||||
namespace. These operations are *conceptually* functional, but
|
||||
not *actually* functional. They don't belong in a pure Hoon
|
||||
expression, but they do belong in a dojo recipe. A recipe is "FP
|
||||
in the large," more like Unix pipes than Haskell monads.
|
||||
|
||||
The dojo is "single-threaded" in each session. One session can
|
||||
work on one command at a time. The session does not accept user
|
||||
input while processing a command, even when it blocks over the
|
||||
network. And each session's state is independent. (If you want
|
||||
to work on two things at a time, connect two console sessions to
|
||||
your dojo.)
|
||||
|
||||
Once you've built your product noun, you show, save, or send it.
|
||||
|
||||
You can pretty-print the product to the console. You can save it
|
||||
-- as a dojo variable, as a revision to the Urbit filesystem, or
|
||||
as an export to a file in the Unix filesystem. Or you can
|
||||
send it -- staying native with an Urbit poke, or going retro
|
||||
with an HTTP PUT/POST.
|
||||
|
||||
All these operations are typed. Hoon is a statically typed
|
||||
language, but the dojo is a dynamic interpreter. The nouns you
|
||||
build in the dojo are dynamically typed nouns, or "cages".
|
||||
|
||||
A cage actually has two layers of type: "mark," a network label
|
||||
(like a MIME type), and "range," a Hoon language type. When a
|
||||
cage is sent across the Urbit network, the receiving daemon
|
||||
validates the noun against its own version of the mark, and
|
||||
regenerates the range.
|
||||
|
||||
Of course, sometimes a recipe produces a noun with mark `%noun`,
|
||||
meaning "any noun," and range `*`, the set of all nouns. We have
|
||||
no choice but to do the best we can with mystery nouns, but we
|
||||
prefer a formal description.
|
||||
|
||||
Marks let us perform a variety of formal typed operations on
|
||||
nouns: validation of untrusted data, format conversion, even
|
||||
patch and diff for revision control.
|
||||
|
||||
#### Other resources
|
||||
|
||||
An excellent way to understand `:dojo` is to read the source,
|
||||
which is in `/===/ape/dojo/hoon`.
|
||||
|
||||
Unfortunately, you may or may not know Hoon. We'll use some Hoon
|
||||
snippets here for defining structures and grammars. Just think
|
||||
of it as pseudocode -- the meaning should be clear from context.
|
||||
|
||||
#### Syntax and semantics
|
||||
|
||||
To use the dojo, type a complete command at the dojo prompt.
|
||||
The simplest command just prints a Hoon expression:
|
||||
|
||||
~urbit-name:dojo> (add 2 2)
|
||||
|
||||
Hit return. You'll see:
|
||||
|
||||
> (add 2 2)
|
||||
4
|
||||
~urbit-name:dojo>
|
||||
|
||||
Similarly in tall form,
|
||||
|
||||
~urbit-name:dojo> %+ add 2 2
|
||||
> %+ add 2 2
|
||||
4
|
||||
~urbit-name:dojo>
|
||||
|
||||
An incomplete command goes into a multiline input buffer. Use
|
||||
the up-arrow (see the console history section) to get the last
|
||||
command back, edit it so it's just `%+ add 2`, and press return.
|
||||
You'll see:
|
||||
|
||||
> %+ add 2
|
||||
~urbit-name/dojo<
|
||||
|
||||
Enter `2`. You'll see:
|
||||
|
||||
> %+ add 2
|
||||
2
|
||||
4
|
||||
~urbit-name/dojo>
|
||||
|
||||
The full command that parses and runs is the concatenation of all
|
||||
the partial lines, with a space inserted between them. To clear
|
||||
all multiline input, just hit return on an empty prompt.
|
||||
|
||||
##### Command structure
|
||||
|
||||
Every finished line is parsed into one `++dojo-command`:
|
||||
|
||||
++ dojo-command ::
|
||||
$% [%edit p=path q=dojo-recipe] :: modify clay file
|
||||
[%http p=? q=purl r=dojo-recipe] :: http post or put
|
||||
[%poke p=goal q=dojo-recipe] :: send request
|
||||
[%save p=path q=dojo-recipe] :: replace clay file
|
||||
[%show p=dojo-recipe] :: print to console
|
||||
[%unix p=path q=dojo-recipe] :: export to unix
|
||||
[%verb p=term q=dojo-recipe] :: store variable
|
||||
== ::
|
||||
|
||||
Each kind of `++dojo-command` is an action that depends on one
|
||||
noun thproduction, a `++dojo-recipe`. We describe first the
|
||||
commands, then the recipes.
|
||||
|
||||
---
|
||||
|
||||
###### `[%show p=dojo-recipe]`
|
||||
|
||||
To print the product, the command is just the recipe:
|
||||
|
||||
~urbit-name:dojo> (add 2 2)
|
||||
|
||||
---
|
||||
|
||||
###### `[%verb p=term q=dojo-recipe]`
|
||||
|
||||
To save the product to a variable `foo`:
|
||||
|
||||
~urbit-name:dojo> =foo (add 2 2)
|
||||
|
||||
`foo` goes into your Hoon subject (scope) and is available to all
|
||||
expressions.
|
||||
|
||||
To unbind `foo`:
|
||||
|
||||
~urbit-name:dojo> =foo
|
||||
|
||||
The dojo has a set of special variables, some read-write and some
|
||||
read-only: `dir`, `lib`, `arc`, `now`, `our`.
|
||||
|
||||
The read-write specials are `dir`, `lib` and `arc`. `dir` is the beak
|
||||
(revision-control branch) and directory this session is operating in,
|
||||
and normally accessed/set with `%`. `lib` is a set of libraries, and
|
||||
`arc` a set of structures, to put in the Hoon subject.
|
||||
|
||||
Read-only specials are `now`, the current (128-bit `@da`) time,
|
||||
and `our`, the current urbit.
|
||||
|
||||
---
|
||||
|
||||
###### `[%edit p=path q=dojo-recipe]`
|
||||
###### `[%save p=path q=dojo-recipe]`
|
||||
|
||||
The product is either a new version of, or a modification to,
|
||||
the Urbit file at the given path. (See the discussion of Urbit
|
||||
filesystem paths.)
|
||||
|
||||
To save:
|
||||
|
||||
~urbit-name:dojo> *%/numbers/four (add 2 2)
|
||||
|
||||
To edit:
|
||||
|
||||
~urbit-name:dojo> -%/numbers/four (add 2 2)
|
||||
|
||||
A save (`*`) overwrites the current (if any) version of the file
|
||||
with a new version of any mark. The save command above will work
|
||||
(if you want `/numbers/four` at your current path).
|
||||
|
||||
An edit (`-`) produces a diff whose mark has to match the diff
|
||||
mark for the current version of the file. The edit command above
|
||||
will not work, because evaluating a Hoon expression like `(add 2
|
||||
2)` just produces a `%noun` mark, ie, an arbitrary noun.
|
||||
|
||||
For either saves or edits, the current version of the file must
|
||||
be the same version specified in the write -- in other words,
|
||||
we can only write to HEAD. If someone else has sneaked in a
|
||||
change since the version specified, the command will fail.
|
||||
|
||||
---
|
||||
|
||||
###### `[%unix p=path q=dojo-recipe]`
|
||||
|
||||
~urbit-name:dojo> ./numbers/four (add 2 2)
|
||||
|
||||
The product is saved as a Unix file (its mark is translated
|
||||
to MIME, and the MIME type is mapped as the extension).
|
||||
|
||||
---
|
||||
|
||||
###### `[%poke p=goal q=dojo-recipe]`
|
||||
|
||||
A poke is a one-way transactional request. It either succeeds
|
||||
and returns no information, or fails and produces an error dump.
|
||||
|
||||
Every poke is sent to one daemon on one urbit. The default urbit
|
||||
is your urbit. The default daemon is the system daemon, `:hood`.
|
||||
The following syntactic forms are equivalent:
|
||||
|
||||
~urbit-name:dojo> :~urbit-name/hood (add 2 2)
|
||||
~urbit-name:dojo> :hood (add 2 2)
|
||||
~urbit-name:dojo> :~urbit-name (add 2 2)
|
||||
~urbit-name:dojo> : (add 2 2)
|
||||
|
||||
Urbit pokes do not have a separate verb. The mark of the message
|
||||
defines the semantics of the operation. You don't call a method
|
||||
`foo` whose argument is a noun in mark `bar` -- you poke a noun
|
||||
in mark `bar`. The mark is the protocol is the method.
|
||||
|
||||
If the poke succeeds, you'll see an `>=` line. If not, you'll
|
||||
see an error report, typically with a stack trace.
|
||||
|
||||
It's common (but not necessary) to use a custom generator for the
|
||||
daemon you're talking to. (For generators, see below.) Hence
|
||||
|
||||
~urbit-name:dojo> :~urbit-name/fish +fish/catch (add 2 2)
|
||||
|
||||
It's irritating to type "fish" twice, just because we're using a
|
||||
fish generator to talk to a fish daemon. Hence a shortcut:
|
||||
|
||||
~urbit-name:dojo> :~urbit-name/fish|catch (add 2 2)
|
||||
|
||||
If we combine all these defaults, we get the "system command"
|
||||
shortcut:
|
||||
|
||||
~urbit-name:dojo> :~urbit-name/hood +hood/reload %ames
|
||||
~urbit-name:dojo> |reload %ames
|
||||
|
||||
This is the most common poke, a generated message to your own
|
||||
hood.
|
||||
|
||||
---
|
||||
|
||||
##### `[%http p=? q=purl r=dojo-recipe]`
|
||||
|
||||
The Web has its own poke, unfortunately in two flavors. To POST,
|
||||
|
||||
~urbit-name:dojo> +http://website.com (add 2 2)
|
||||
|
||||
To PUT:
|
||||
|
||||
~urbit-name:dojo> -http://website.com (add 2 2)
|
||||
|
||||
As with a poke, you'll get a >= for success, or an error report.
|
||||
|
||||
---
|
||||
|
||||
##### Recipes, models and filters
|
||||
|
||||
But wait, what's a recipe? Simplifying the actual code slightly:
|
||||
|
||||
++ dojo-recipe :: functional build
|
||||
$% [%ex p=twig] :: hoon expression
|
||||
[%as p=mark q=dojo-recipe] :: conversion
|
||||
[%do p=twig q=dojo-recipe] :: apply gate
|
||||
[%ge p=dojo-script] :: generator
|
||||
[%ur p=purl] :: get url
|
||||
[%tu p=(list dojo-recipe)] :: tuple
|
||||
== ::
|
||||
++ dojo-script :: script
|
||||
$: p=path :: core recipe
|
||||
q=dojo-config :: configuration
|
||||
== ::
|
||||
++ dojo-config :: configuration
|
||||
$: p=(list dojo-recipe) :: by order
|
||||
q=(map term (unit dojo-recipe)) :: by keyword
|
||||
== ::
|
||||
|
||||
---
|
||||
|
||||
###### `[%ex p=twig]`
|
||||
|
||||
The twig in an `%ex` recipe is a Hoon expression. The recipe
|
||||
syntax is just the Hoon syntax.
|
||||
|
||||
The subject of the twig is a core stack: first the Hoon kernel,
|
||||
then the Arvo standard library, then the structures and libraries
|
||||
in `lib` and `arc`. On the very top are the dojo variables.
|
||||
|
||||
A twig produces the trivial mark `%noun`, except in two cases
|
||||
where the dojo can do better. The dojo analyzes the twig to
|
||||
detect two trivial cases where direct evaluation gives us a mark:
|
||||
a variable reference like `foo` that matches a dojo variable, or
|
||||
an urbitspace dereference like `.^(/cx/~urbit-name/main/1/foo)`.
|
||||
|
||||
---
|
||||
|
||||
###### `[%tu p=(list dojo-recipe)]`
|
||||
|
||||
A is just a tuple of recipes, using the normal Hoon syntax for
|
||||
a tuple. `[a]` is `a`, `[a b]` the cell `[a b]`, `[a b c]` the
|
||||
cell `[a [b c]]`.
|
||||
|
||||
A tuple, unless it's a trivial 1-tuple, is always marked `%noun`.
|
||||
|
||||
---
|
||||
|
||||
###### `[%ge p=dojo-script]`
|
||||
|
||||
A `%ge` is a generator, a configurable script loaded from the
|
||||
filesystem.
|
||||
|
||||
The script recipe `++dojo-script` specifies a script path, a list
|
||||
of ordered arguments, and a list of keyword arguments. All the
|
||||
arguments are recipes. The path specifies a Hoon source file in
|
||||
`/===/gen/[path]`.
|
||||
|
||||
For the path `/fun/make`, the ordered arguments `1`, `2` and `3`,
|
||||
and the named arguments `foo` and `bar`, the syntax is:
|
||||
|
||||
~urbit-name:dojo> +fun/make 1 2 3, =foo (add 2 2), =bar 42
|
||||
|
||||
Unless this non-closed form is the end of a command, it needs to
|
||||
be surrounded by `[]` to make it play well with others.
|
||||
|
||||
Generator programming is covered in the dojo developer's guide.
|
||||
The user doesn't need to know or notice how the generator gets
|
||||
its input (if any), except in one case: a dialog.
|
||||
|
||||
A dialog generator will take over the prompt and ask you
|
||||
questions. If this seems terrifying, ^D will abort the dialog,
|
||||
the recipe, and the command, and take you back to the dojo.
|
||||
|
||||
---
|
||||
|
||||
###### `[%as p=mark q=dojo-recipe]`
|
||||
|
||||
`%as` is a mark conversion. Since the input to it is another
|
||||
recipe, we can chain them to make a conversion pipeline.
|
||||
|
||||
To convert a recipe, just precede it with the converison form, `&mark`:
|
||||
|
||||
~urbit-name:dojo> &noun (add 2 2)
|
||||
~urbit-name:dojo> &md (add 50 7)
|
||||
|
||||
---
|
||||
|
||||
###### `[%do p=twig q=dojo-recipe]`
|
||||
|
||||
`%do` is a Hoon functino (gate) application. It can also be in a pipeline.
|
||||
|
||||
Its syntax is a hoon expression preceeded by `_`:
|
||||
|
||||
~urbit-name:dojo> _lore 'hello\0aworld'
|
||||
~urbit-name:dojo> _|=(a=@ (mul 3 a))} (add 2 2)
|
||||
|
||||
---
|
||||
|
||||
###### `[%ur p=purl]`
|
||||
|
||||
A simple HTTP get produces the result as a `%httr` noun.
|
||||
|
||||
---
|
||||
|
||||
### Development
|
||||
|
||||
Developing dojo generators is the easiest form of Hoon programming.
|
||||
Generator scripts are found in the `gen` folder.
|
||||
|
||||
#### Configuration
|
||||
|
||||
All generator scripts are configured with the same configuration gate:
|
||||
|
||||
|= $: [now=@da eny=@ bec=beak]
|
||||
[arg=my-arguments opt=my-options]
|
||||
==
|
||||
|
||||
We try to minimize boilerplate, but you can't get out of this
|
||||
one. The dojo will slam this configuration gate to create your
|
||||
generator.
|
||||
|
||||
The head of the sample is a system context. `now` is the date of
|
||||
the call; `eny` is 256 bits of entropy; `bec` is a triple
|
||||
`[p=ship q=desk r=case]` (ie, the root of a filesystem path).
|
||||
This beak is the path to the script, not the current path within
|
||||
the dojo (dojo variables are not, unlike in Unix, visible to
|
||||
generator scripts).
|
||||
|
||||
`arg` and `opt` are whatever you want them to be. (Use `~` if
|
||||
you have no arguments or options.) The dojo will replace `arg`
|
||||
with the user's ordered arguments, and replace any options in
|
||||
`opt` specified by the user's named arguments. (More exactly,
|
||||
if the user specifies `=foo 42`, your `opt` is replaced with
|
||||
`opt(foo 42)`.)
|
||||
|
||||
Bear in mind that dojo syntax is list-centric, so your `arg` will
|
||||
always end with a `~`. For instance,
|
||||
|
||||
~urbit-name/dojo> +fun/make 1 2 3
|
||||
|
||||
will generate an `arg` of `[1 2 3 ~]`. Yes, this is the only
|
||||
place in Urbit where we do list-centric arguments.
|
||||
|
||||
Note also that script configuration is typed. The user's command
|
||||
will fail if there's a type mismatch. But `arg` does not have to
|
||||
be a homogeneous list -- just a tuple with `~` on the end. Also,
|
||||
you can use `arg=*` and sort out the nouns by hand.
|
||||
|
||||
You can also use `*` anywhere if you're not interested in the
|
||||
system context, or in
|
||||
|
||||
#### Generators
|
||||
|
||||
There are three kinds of generators: builders (with no special
|
||||
I/O), dialogs (which read console input), and scrapers (which
|
||||
pull data from the webs). Any generator can use `.^` to both
|
||||
read from and block (wait for remote or delayed results) on
|
||||
the Urbit global namespace.
|
||||
|
||||
A generator produces a cell whose tail is the configuration gate,
|
||||
and whose head is either `%say` for a builder, `%ask` for a
|
||||
dialog, or `%get` for a scraper.
|
||||
|
||||
(If you want to write one generator which both prompts the user
|
||||
and scrapes the web, don't. Write two, and configure the second
|
||||
with the result of the first. We pay a price for keeping things
|
||||
stupid.)
|
||||
|
||||
##### Builders
|
||||
|
||||
A builder just produces a cask (mark-value cell) directly from
|
||||
the configuration gate. Here's the simplest builder, with a
|
||||
blank configuration:
|
||||
|
||||
:- %say |= *
|
||||
:- %noun
|
||||
"hello, world."
|
||||
|
||||
##### Dialogs
|
||||
|
||||
A dialog is a console input form. We recommend using the helpful
|
||||
`sole` structures, with
|
||||
|
||||
/- *sole
|
||||
|
||||
(If you're interested in building your own dialogs without `sole`
|
||||
(not that complicated at all), it's easiest to start by
|
||||
reverse-engineering `sole`.)
|
||||
|
||||
Otherwise, a dialog boilerplate (with blank configuration), which
|
||||
generates a `my-result` result with mark `%my-result-mark`:
|
||||
|
||||
:- %ask |= *
|
||||
^- (sole-result (cask my-result))
|
||||
%+ sole-so %my-result-mark
|
||||
*my-result
|
||||
|
||||
Internally, a `++sole-result` is either a final result or a new
|
||||
dialog core with a new prompt. The dojo keeps running the dialog
|
||||
until it produces a final result.
|
||||
|
||||
A dialog step can do one of three things: print a message, prompt
|
||||
for a value, or finish with a result. These are done with
|
||||
`sole-yo`, `sole-lo`, and `sole-so` respectively. Here's a
|
||||
simple dialog which uses all of them:
|
||||
|
||||
:- %ask |= *
|
||||
^- (sole-result (cask ,@ud))
|
||||
%+ sole-yo leaf/"let's multiply two numbers..."
|
||||
%+ sole-lo [%& %number "number one: "]
|
||||
%+ sole-go dim:ag
|
||||
|= one=@ud
|
||||
%+ sole-lo [%& %number "number two: "]
|
||||
%+ sole-go dim:ag
|
||||
|= two=@ud
|
||||
%+ sole-so %noun
|
||||
(mul one two)
|
||||
|
||||
`++sole-yo` prints a tank (prettyprint structure). See `++tank`
|
||||
in hoon.hoon.
|
||||
|
||||
`++sole-lo` takes a prompt and a new dialog. In the example,
|
||||
`[%& %number "your number: "]` is a `++sole-prompt`. `&` as
|
||||
opposed to `|` means input is echoed (not a password).
|
||||
`%number` is a history label; all inputs with the same label
|
||||
share the same history buffer.
|
||||
|
||||
The `++sole-dialog` is generally built with `++sole-go`, as used
|
||||
above. This takes a parsing `++rule` (here `dim:ag`, which
|
||||
parses a decimal), and a gate whose sample is the parsed value,
|
||||
producing a new dialog.
|
||||
|
||||
##### Scrapers
|
||||
|
||||
Most stuff on the internets is crap, but there's exceptions.
|
||||
Sometimes it's nice to get it and compute functions on it.
|
||||
|
||||
A scraper is much like a dialog, except instead of `sole-lo` and
|
||||
`sole-go` it uses `sole-at`.
|
||||
|
||||
:- %get |= *
|
||||
%+ sole-yo leaf/"Fetching example.com"
|
||||
%+ sole-at [[& ~ `/com/example] `/ ~]
|
||||
|= hit=httr
|
||||
%+ sole-yo leaf/"Fetched."
|
||||
%+ sole-so %httr
|
||||
hit
|
||||
|
||||
`++sole-at` takes a `purl` request url, and a gate through
|
||||
which to slam the result `httr`.
|
287
pub/doc/tools/talk.md
Normal file
287
pub/doc/tools/talk.md
Normal file
@ -0,0 +1,287 @@
|
||||
# `:talk`
|
||||
|
||||
`:talk` is the Urbit appliance for chatter and notifications.
|
||||
For less sophisticated users, Urbit *is* just `:talk`. If you
|
||||
see `:talk` as "like Slack, but distributed," or "like IRC, but
|
||||
persistent and encrypted," you're not completely wrong.
|
||||
|
||||
`:talk` is an unusual messenger in two ways. One: by default, it
|
||||
multiplexes all content streams into a single flow. Most UI
|
||||
researchers agree that context-switching is cognitively expensive
|
||||
and leads to surfing the Internet. (`:talk` is also used for
|
||||
your system notifications.)
|
||||
|
||||
Two: text lines are limited to 64 ASCII bytes, no uppercase.
|
||||
This restriction is mobile-friendly and reduces the aesthetic
|
||||
impact of low-quality content.
|
||||
|
||||
Messages in `:talk` are called "posts". Posts go to "stations,"
|
||||
which are just like IRC or Slack channels. Any urbit can host or
|
||||
subscribe to any number of stations.
|
||||
|
||||
`:talk` is not a text-only messenger; it's designed to support
|
||||
arbitrary content in posts, from URLs to images to long-form
|
||||
text. (Only URLs right now.) However, any message on `:talk`
|
||||
has to be able to summarize itself in a 64-byte text line.
|
||||
|
||||
There are four kinds of station: a write-only "mailbox" for
|
||||
direct messages, an invite-only "party" for private conversation,
|
||||
a read-only "journal" for curated content, and a public-access
|
||||
"board" for general use or abuse.
|
||||
|
||||
While there's obviously no central `:talk` server for all of
|
||||
Urbit, and thus no such thing as a truly global station space,
|
||||
active Urbit stars cooperate to federate, manage and mirror a
|
||||
collectively-managed namespace, very like Usenet. These
|
||||
"federal" stations are generally public-access boards.
|
||||
|
||||
### Quickstart
|
||||
|
||||
Let's post something! At the default `:talk` prompt
|
||||
```
|
||||
~tasfyn-partyv:talk:
|
||||
```
|
||||
type the message:
|
||||
```
|
||||
~tasfyn-partyv:talk: hello, world.
|
||||
```
|
||||
And hit return. Don't worry, no one but you will see this. The
|
||||
`:` means you're posting to yourself. You'll get the post:
|
||||
```
|
||||
~tasfyn-partyv: hello, world.
|
||||
~tasfyn-partyv:talk:
|
||||
```
|
||||
It's boring to post to yourself. Let's join a station:
|
||||
```
|
||||
~tasfyn-partyv: ;join /urbit-test
|
||||
```
|
||||
(`/urbit-test` is a federal station, meaning it's hosted by your
|
||||
star (for `~tasfyn-partyv`, `~doznec`). The `/` notation is just
|
||||
an abbreviation for `~doznec/urbit-test`.)
|
||||
|
||||
You'll see:
|
||||
```
|
||||
---------:talk| %porch subscribed to /urbit-test, called `>`
|
||||
---------:talk| rules of /urbit-test:
|
||||
---------:talk| test posts only. no shitposting. no pedos/nazis.
|
||||
~doznec> ~tasfyn-partyv admitted to %urbit-test
|
||||
~tasfyn-partyv:talk>
|
||||
```
|
||||
Notice the character assignment - stations you're subscribed to are
|
||||
assigned [consistent ASCII glyphs](#-station-glyphs), which you'll
|
||||
see in the log when you hear from these stations, and on the prompt
|
||||
when you're talking to them.
|
||||
|
||||
Post a line to `/urbit-test`:
|
||||
```
|
||||
~tasfyn-partyv:talk> hello, world
|
||||
```
|
||||
You'll see, echoed back at you through `~doznec`:
|
||||
```
|
||||
~tasfyn-partyv:talk> hello, world
|
||||
```
|
||||
And of course, anyone else in `/urbit-test` will see it as well.
|
||||
But you don't care about `/urbit-test`, so leave it:
|
||||
```
|
||||
~tasfyn-partyv:talk> ;leave
|
||||
```
|
||||
You'll see:
|
||||
```
|
||||
---------:talk| %porch has left /urbit-test, called `>`
|
||||
```
|
||||
Everyone else will see:
|
||||
```
|
||||
~doznec> ~tasfyn-partyv has left %urbit-test
|
||||
```
|
||||
|
||||
Now you're ready to use `:talk` for real! List the federal
|
||||
groups currently available with
|
||||
```
|
||||
~tasfyn-partyv:talk> ;list
|
||||
```
|
||||
For general discussion about Urbit, we recommend `/urbit-meta`.
|
||||
|
||||
### Basic usage
|
||||
|
||||
#### Input conventions
|
||||
|
||||
There are three kinds of inputs you can type at the `:talk`
|
||||
prompt: lines, URLs, and commands.
|
||||
|
||||
A line is 64 bytes of ASCII lowercase and spaces. If the line
|
||||
starts with '@', it's an action (IRC `/me`).
|
||||
|
||||
The `:talk` interface will let you keep typing past 64 bytes, but
|
||||
insert a Unicode bullet-point character in an appropriate space
|
||||
in your post, to show you the prospective linebreak. Your essay
|
||||
will be posted in multiple lines.
|
||||
|
||||
A URL is any valid URL. A command is any line starting with `;`.
|
||||
|
||||
#### Source annotation
|
||||
|
||||
Any post in your flow is shown with its author, together with a
|
||||
glyph that shows how the post reached you. A post can reach you
|
||||
in one of three ways:
|
||||
|
||||
Any post you see reached you in one of three ways. Either it was
|
||||
sent directly to just you; to you and others; or to a station you
|
||||
subscribe to.
|
||||
|
||||
Posts to just you are `:`. Posts to you and others (a multiparty
|
||||
conversation) are `*`, unless you've bound this conversation to a
|
||||
glyph. Posts to a station use that station's glyph.
|
||||
|
||||
You can see a list of glyph bindings with `;what`. Write
|
||||
`;what >` to see what station `>` is bound to, or
|
||||
`;what /urbit-test` to see if `/urbit-test` has a binding.
|
||||
|
||||
#### Audience selection
|
||||
|
||||
Audience selection is important in a multiplexed communicator!
|
||||
The audience is always shown in your prompt. If there's a glyph
|
||||
for it, it's shown as the glyph:
|
||||
```
|
||||
~tasfyn-partyv:talk>
|
||||
```
|
||||
Otherwise, the audience is shown in parens:
|
||||
```
|
||||
~tasfyn-partyv:talk(~wictuc-folrex)
|
||||
```
|
||||
|
||||
`:talk` works fairly hard to get the audience right and minimize
|
||||
manual switching. But to manually set the audience, the command
|
||||
is simply `;station` - eg, `;~wictuc-folrex` for a direct post;
|
||||
`/urbit-test` or `~doznec/urbit-test` to post to a federal
|
||||
station, `%mystation` to post to a station on your own ship.
|
||||
For a station bound to a glyph, `;` then the glyph; eg, `;>`.
|
||||
|
||||
You can post a line and set the audience in one command, eg:
|
||||
```
|
||||
;~wictuc-folrex this is a private message
|
||||
```
|
||||
|
||||
You can configure your audience in a number of ways, which are
|
||||
applied in priority order. From strongest to weakest:
|
||||
|
||||
- if typing a post, the audience when you started typing.
|
||||
- if you activated a post (see below), the post you activated.
|
||||
- if you manually locked the audience (see above), that audience.
|
||||
- audience of the last post received.
|
||||
- audience of the last post sent.
|
||||
|
||||
You can clear any audience setting layer by moving your cursor to
|
||||
the start of the line and pressing backspace (whether the line is
|
||||
empty or not). Posting a line clears the typing and activation
|
||||
configurations.
|
||||
|
||||
#### Post activation and numbering
|
||||
|
||||
Every post can summarize itself in 64 bytes. But some posts
|
||||
contain more information, which is not displayed by default.
|
||||
Displaying this "attachment" is an opt-in operation. In the
|
||||
post, it's marked by an underscore `_`, instead of a space,
|
||||
between source and content.
|
||||
|
||||
The conventional example is a URL. When you post a URL:
|
||||
```
|
||||
~tasfyn-partyv:talk> http://foobar.com/moo/baz
|
||||
```
|
||||
This will appear in the flow as:
|
||||
```
|
||||
~tasfyn-partyv>_foobar.com
|
||||
```
|
||||
meaning that `~tasfyn-partyv` posted a link to `foobar.com`,
|
||||
on the station or conversation whose glyph is `>`.
|
||||
|
||||
The effect of activating a post depends on the post. For a link,
|
||||
the full URL is shown and (system permitting) put into the OS's
|
||||
clipboard, or even automatically navigated to. Even for a text
|
||||
post, activating shows the full audience, for complex audiences.
|
||||
|
||||
Posts in your `:talk` flow are numbered; the numbers are printed
|
||||
every five posts, as
|
||||
```
|
||||
----------[5955]
|
||||
```
|
||||
You can specify a post to activate in two ways: by absolute or
|
||||
relative position. Absolute position is a direct history number:
|
||||
```
|
||||
;5955
|
||||
```
|
||||
If you use fewer digits than are in the current flow number, the
|
||||
high digits are defaulted "deli style" - if the current number is
|
||||
5955, typing `;3` means `;5953`, and `;140` means `;5140`. To
|
||||
actually activate post `3`, write `;0003`.
|
||||
|
||||
A unary sequence of `;` characters looks backward from the
|
||||
present. `;` activates the most recent post; `;;` the second
|
||||
most recent; etc.
|
||||
|
||||
#### Nicknames
|
||||
|
||||
Sometimes you know your Urbit friends by other names, on or
|
||||
offline. Use the `;nick` command to assign or look up
|
||||
nicknames.
|
||||
|
||||
`;nick` with no arguments lists all nicknames; `;nick
|
||||
~tasfyn-partyv` looks up a nickname; `;nick curtis` searches in
|
||||
reverse; `;nick ~tasfyn-partyv curtis` creates a nickname.
|
||||
All nicknames must be 14 characters or less, lowercase.
|
||||
|
||||
Of course, nicknames are strictly local - like the names on
|
||||
entries in a phonebook. Sometimes in a post you want to mention
|
||||
someone you know by a nickname. Just type `~curtis`, and `:talk`
|
||||
will replace it magically with `~tasfyn-partyv` (or beep if no
|
||||
`~curtis` is bound).
|
||||
|
||||
#### Presence
|
||||
|
||||
You'll see presence notifications when people enter or leave
|
||||
stations you're subscribed to.
|
||||
|
||||
`;who` lists everyone in all your stations. `;who station`
|
||||
lists everyone in that station.
|
||||
|
||||
#### Typing indicator
|
||||
|
||||
If one or more urbits in your audience is typing, `:talk`'s
|
||||
presence system will detect it and change the prompt:
|
||||
```
|
||||
~tasfyn-partyv [~wictuc-folrex...]>
|
||||
```
|
||||
|
||||
#### Creating and managing stations
|
||||
|
||||
To create your own mailbox, party, journal or board:
|
||||
```
|
||||
;create party %myfunparty
|
||||
;create journal %serious-journal
|
||||
;create board %bizarre-board
|
||||
```
|
||||
etc.
|
||||
|
||||
Every form of station has an exception list; to block
|
||||
`~wictuc-folrex` from your default mailbox `%porch`,
|
||||
```
|
||||
;block %porch ~wictuc-folrex
|
||||
```
|
||||
To invite people to `%myfunparty`:
|
||||
```
|
||||
;invite %myfunparty ~wictuc-folrex, ~sondel-forsut
|
||||
```
|
||||
To ban from `%bizarre-board`:
|
||||
```
|
||||
;banish %bizarre-board ~wictuc-folrex
|
||||
```
|
||||
To appoint a coauthor of `%serious-journal`:
|
||||
```
|
||||
;author %serious-journal ~sondel-forsut
|
||||
```
|
||||
|
||||
#### Station Glyphs
|
||||
|
||||
Station are assigned out of the list `:|}>`, then
|
||||
randomly out of it and the sets `-+*.`, ``,=`'^\/``,
|
||||
`$%&@`, and `{<[]()`, in decreasing order of probabilty.
|
||||
Alphanumeric characters and `!#?;~_` are reserved.
|
50
pub/doc/tools/tree.md
Normal file
50
pub/doc/tools/tree.md
Normal file
@ -0,0 +1,50 @@
|
||||
# `:tree`
|
||||
|
||||
`:tree` is the web filesystem interface.
|
||||
|
||||
`:tree` is a single-page app that uses a backend in `/home/tree` to load contents from `%clay` as the user navigates around as `%json`. The frontend lives in `/home/pub/tree` and is a fairly straightforward [React](https://facebook.github.io/react/) / [Flux](https://facebook.github.io/flux/) app.
|
||||
|
||||
## Frontend
|
||||
|
||||
The frontend code for `:tree` can be found in `/home/pub/tree/src/`.
|
||||
|
||||
### CSS
|
||||
|
||||
The CSS is written in [Stylus](https://learnboost.github.io/stylus/). The main entry point is `main.styl` and can be compiled with `stylus main.styl` which should output a `main.css`
|
||||
|
||||
### JS
|
||||
|
||||
The JS is written in [CoffeeScript](http://coffeescript.org/) and packaged with [Browserify](http://browserify.org/). The main entry point is `main.coffee` and is compiled with `browserify -t coffeeify main.coffee > main.js`. You'll need to `npm install` first.
|
||||
|
||||
Each page is loaded as JSON and then rendered using React on the page. This allows us to write JSX in our markdown to implement simple components. Check out `/home/pub/tree/src/js/components` to see the component library.
|
||||
|
||||
You'll notice that some of these doc pages use things like `<list>` in the raw markdown files.
|
||||
|
||||
## JSON API
|
||||
Async provides loading by schema
|
||||
|
||||
`{path name sein sibs next prev}` are all immediately accesible from the store
|
||||
|
||||
a `getPath` method, if present (defaulting to current url), is used to determine the query root node.
|
||||
|
||||
## JSON Internals
|
||||
|
||||
### `/[desk]/tree/{path}.json`
|
||||
`tree/json.hook` accepts a query string schema `q` in light noun encoding
|
||||
|
||||
++ schema (dict ,[term $|(mark schema)])
|
||||
++ dict |*(a=_,* $&([a (dict a)] a))
|
||||
|
||||
which is normalized and type-checked to a `query` list of
|
||||
- `[%kids query]`, the only recursive value, which executes for all subpaths
|
||||
XX descent is only currently supported to a single level as a performance optimization
|
||||
- `[%name %t]`, the node name
|
||||
- `[%path %t]`, the current path
|
||||
- `[%snip %r]`, a snippet, extracted via `react-snip-json`
|
||||
- `[%head %r]`, the first `<h1/>`, extracted via `react-head-json`
|
||||
- `[%body %r]`, the `react-json` body
|
||||
- `[%meta %j]`, json frontmatter per the `mdy` mark definition
|
||||
|
||||
The request types above are `%t` text, `%r` html-derived tree, and `%j`
|
||||
arbitrary json; an example query, used by the main content renderer, is
|
||||
`"q=body.r__kids_name.t"` (`body:'r' kids:{name:'t'}` )
|
@ -1,34 +0,0 @@
|
||||
# Tree
|
||||
|
||||
`:tree` is the web filesystem interface.
|
||||
|
||||
# Data retrieval interface
|
||||
Async provides loading by schema
|
||||
|
||||
`{path name sein sibs next prev}` are all immediately accesible from the store
|
||||
|
||||
a `getPath` method, if present (defaulting to current url), is used to determine the query root node.
|
||||
|
||||
# Internals
|
||||
|
||||
something something coffeescript
|
||||
|
||||
## `/[desk]/tree/{path}.json`
|
||||
tree/json.hook accepts a query string schema `q` in light noun encoding
|
||||
|
||||
++ schema (dict ,[term $|(mark schema)])
|
||||
++ dict |*(a=_,* $&([a (dict a)] a))
|
||||
|
||||
which is normalized and type-checked to a `query` list of
|
||||
- `[%kids query]`, the only recursive value, which executes for all subpaths
|
||||
XX descent is only currently supported to a single level as a performance optimization
|
||||
- `[%name %t]`, the node name
|
||||
- `[%path %t]`, the current path
|
||||
- `[%snip %r]`, a snippet, extracted via `react-snip-json`
|
||||
- `[%head %r]`, the first `<h1/>`, extracted via `react-head-json`
|
||||
- `[%body %r]`, the `react-json` body
|
||||
- `[%meta %j]`, json frontmatter per the `mdy` mark definition
|
||||
|
||||
The request types above are `%t` text, `%r` html-derived tree, and `%j`
|
||||
arbitrary json; an example query, used by the main content renderer, is
|
||||
`"q=body.r__kids_name.t"` (`body:'r' kids:{name:'t'}` )
|
@ -37,12 +37,17 @@ module.exports = query {
|
||||
elem = @props.kids[item]
|
||||
href = window.tree.basepath path
|
||||
parts = []
|
||||
title = null
|
||||
if elem.meta?.title
|
||||
title =
|
||||
gn: 'h1'
|
||||
c: [elem.meta.title]
|
||||
else title = elem.head
|
||||
title ||= (h1 {},item)
|
||||
if elem.head.c.length > 0
|
||||
title = elem.head
|
||||
if not title
|
||||
title =
|
||||
gn: 'h1'
|
||||
c: [item]
|
||||
parts.push title
|
||||
unless @props.titlesOnly # redundant? this seems familiar
|
||||
if @props.dataPreview
|
||||
|
@ -646,15 +646,22 @@ module.exports = query({
|
||||
elem = this.props.kids[item];
|
||||
href = window.tree.basepath(path);
|
||||
parts = [];
|
||||
title = null;
|
||||
if ((ref4 = elem.meta) != null ? ref4.title : void 0) {
|
||||
title = {
|
||||
gn: 'h1',
|
||||
c: [elem.meta.title]
|
||||
};
|
||||
} else {
|
||||
}
|
||||
if (elem.head.c.length > 0) {
|
||||
title = elem.head;
|
||||
}
|
||||
title || (title = h1({}, item));
|
||||
if (!title) {
|
||||
title = {
|
||||
gn: 'h1',
|
||||
c: [item]
|
||||
};
|
||||
}
|
||||
parts.push(title);
|
||||
if (!this.props.titlesOnly) {
|
||||
if (this.props.dataPreview) {
|
||||
|
Loading…
Reference in New Issue
Block a user