├── binary-tree-faces.png ├── binary-tree-slots.png ├── hoon-talk-slides.pdf ├── leblancs-triangle.png ├── hoon-talk.md └── index.html /binary-tree-faces.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/famousj/hoon-lc2018/HEAD/binary-tree-faces.png -------------------------------------------------------------------------------- /binary-tree-slots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/famousj/hoon-lc2018/HEAD/binary-tree-slots.png -------------------------------------------------------------------------------- /hoon-talk-slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/famousj/hoon-lc2018/HEAD/hoon-talk-slides.pdf -------------------------------------------------------------------------------- /leblancs-triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/famousj/hoon-lc2018/HEAD/leblancs-triangle.png -------------------------------------------------------------------------------- /hoon-talk.md: -------------------------------------------------------------------------------- 1 | # Hoon and You - An FP Perspective 2 | 3 | [//]: # (Version 25K {% raw %}) 4 | 5 | ## Introduction 6 | 7 | _Delivered as a talk, [LambdaConf 2018](https://lambdaconf2018.lambdaconf.us/en/program-schedule/program/37/hoon-and-you-a-functional-programming-perspective)_ 8 | 9 | This article is about the programming language Hoon, its philosophical and technological underpinnings. 10 | 11 | When you finish reading this, you will probably not be able to do anything in Hoon that you didn't already walk in here knowing. 12 | 13 | But Hoon is completely unlike any other functional programming language you might know, and so we can use it to take a look at familiar concepts from a completely different angle. Things like types. 14 | 15 | Along the way, we'll look at programming language design and what kinds of tradeoffs go into it. 16 | 17 | There are a lot of really smart and talented people involved in this project. I am not one of them. But I've had to dumb down the material to the point that I was able to understand it, and hopefully you'll be able to profit from that effort. 18 | 19 | So, let's get to it, by first talking about what Hoon is for. 20 | 21 | ## The _Telos_ of Hoon 22 | 23 | Much as C was developed to write Unix, Hoon was developed to write Urbit. So we should first mention in passing what this Urbit thing is. 24 | 25 | Urbit is the somewhat ambitious project of condemning all computing as we know it to complete obsolescence. It's a clean-slate system software stack, which includes the following: 26 | 27 | - OS 28 | - networking 29 | - security 30 | - typed file system with built-in version control 31 | - identity management 32 | - addressing 33 | - several dozen other things 34 | 35 | And when I say "clean-slate", the operating metaphor has been to imagine they find a spaceship from Mars crash-landed outside of Bozeman, MT. What kind of software would they be running? (Spoiler: probably not Windows ME.) 36 | 37 | There's not a single assumption about how computer systems should work that has not been revisited. 38 | 39 | If you stop occasionally and ask, "Why did they do it that way?", it's a very rewarding project to study. Although this can lead you into a rabbit hole. 40 | 41 | ## Kelvin Versioning 42 | 43 | For instance, they use something called "Kelvin versioning". Instead of versions increasing, they decrease, toward zero. They use this for Hoon as well as Nock, the lowest level language, which we'll discuss shortly. 44 | 45 | Why is this? Why not just increment versions like everyone else does? 46 | 47 | The answer is, because software improvement is usually considered an additive process. Version 2.0 does more stuff than version 1.0, version 2.1 is a lot like version 2.0, except it works better. We can assume there will be a version 3.0 sometime in the future which will work differently in some fundamental way, hopefully better but often not. 48 | 49 | Usually this is sensible. We launch software in an environment of uncertainty. 50 | 51 | As time goes on, we understand the problem space better, our users gain some sophistication, technology changes, so we refine the software to match our better understanding. 52 | 53 | Or sometimes decisions come straight from marketing. 54 | 55 | > _CloudTron v4_ 56 | > - Now with Blockchain! 57 | > - Add a rich, smoky bacon scent to all your cloud-stored files. 58 | 59 | ## JSON 60 | 61 | But why do we assume that progress means change? 62 | 63 | Take JSON. JSON is designed for packaging data as text to send between clients and servers. And by design, it has barely changed since the moment it came out. 64 | 65 | I can imagine my great-grandchildren using JSON that looks exactly like the JSON I use. Which means, if I need to send out data from a server I'm writing, if I use JSON, whatever else I might have to change or update or fix, the actual format the data goes out in will not change at all. 66 | 67 | So maybe I might want to do things differently. Personally, I'd allow an optional trailing comma at the end of arrays. 68 | 69 | But any minor improvements you might now make to JSON have to be weighed against the immense convenience of having this entire part of your code that never has to change. 70 | 71 | Whatever else you might need in CloudTron v.5, it won't need to support a new version of JSON. 72 | 73 | And that's just the problem of formatting data as text. 74 | 75 | Now imagine the language, the libraries, the network stack, the entire OS never changes. Imagine never being in dependency hell because everything you're working with is effectively "done" except this app you just had an idea for. 76 | 77 | So to pull ourselves out of this rabbit-hole, maybe sometimes the ideal solution to a problem is software that _stops_ changing. You define its scope as something achievable, and "progress" means decreasing the number of things that have to change, because everything else is working like it's supposed to. 78 | 79 | In other words, it's being frozen. Hence, Kelvin versioning. We assume some Platonic ideal solution, set that version to 0K, and our versions are getting ever closer this ideal. 80 | 81 | ### Simplicity 82 | 83 | To make a software system that can be frozen, it has to be simple. 84 | 85 | You have to be ruthless in your pursuit of simplicity. You have to make hard and fast choices about what is or isn't in scope. If it's not in scope, do the absolute minimum you can with it. 86 | 87 | This way, once you do tackle this bit of functionality, you'll have the most options available to you. 88 | 89 | This is a solution to the [Law of Leaky Abstractions](https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/): 90 | 91 | > If there's no functionality to abstract away, there's nothing to leak. 92 | 93 | Ultimately, you can build something simple from something else that's simple. You cannot build something simple from something complex. 94 | 95 | The best you can hope for is to find a way to shift some of the complexity away from some users, at the cost of making things vastly more complicated for other users. 96 | 97 | Maybe you'll end up with a simple, elegant front-end and a nightmarish back-end. Or vice versa. 98 | 99 | ### Nock 100 | 101 | So what is the simplest basis for a computing system that can be frozen? 102 | 103 | The Urbit answer for this is Nock. Here' is Nock 4K. Functional assembly language: 104 | 105 | ``` 106 | A noun is an atom or a cell. An atom is a natural number. A cell is an ordered pair of nouns. 107 | 108 | nock(a) *a 109 | [a b c] [a [b c]] 110 | 111 | ?[a b] 0 112 | ?a 1 113 | +[a b] +[a b] 114 | +a 1 + a 115 | =[a a] 0 116 | =[a b] 1 117 | =a =a 118 | 119 | /[1 a] a 120 | /[2 a b] a 121 | /[3 a b] b 122 | /[(a + a) b] /[2 /[a b]] 123 | /[(a + a + 1) b] /[3 /[a b]] 124 | /a /a 125 | 126 | #[1 a b] a 127 | #[(a + a) b c] #[a [b /[(a + a + 1) c]] c] 128 | #[(a + a + 1) b c] #[a [/[(a + a) c] b] c] 129 | #a #a 130 | 131 | *[a [b c] d] [*[a b c] *[a d]] 132 | 133 | *[a 0 b] /[b a] 134 | *[a 1 b] b 135 | *[a 2 b c] *[*[a b] *[a c]] 136 | *[a 3 b] ?*[a b] 137 | *[a 4 b] +*[a b] 138 | *[a 5 b] =*[a b] 139 | 140 | *[a 6 b c d] *[a 2 [0 1] 2 [1 c d] [1 0] 2 [1 2 3] [1 0] 4 4 b] 141 | *[a 7 b c] *[a 2 b 1 c] 142 | *[a 8 b c] *[a 7 [[7 [0 1] b] 0 1] c] 143 | *[a 9 b c] *[a 7 c 2 [0 1] 0 b] 144 | *[a 10 [b c] d] *[a 8 c 7 [0 3] d] 145 | *[a 10 b c] *[a c] 146 | *[a 12 [b c] d] #[b *[a c] *[a d]] 147 | 148 | *a *a 149 | ``` 150 | 151 | It is very, very simple. 152 | 153 | The data model is: 154 | 155 | ``` 156 | A noun is an atom or a cell. An atom is any natural number. 157 | A cell is any ordered pair of nouns. 158 | ``` 159 | 160 | Basically a noun is a binary tree whose leaves are numbers, unsigned integers of arbitrary length. Someone described the noun as an "S-Expression without the S", since Nock takes no interest in what the atom is. 161 | 162 | ``` 163 | nock(a) *a 164 | ``` 165 | 166 | This line notes that nock is a function, which makes sense, this being functional assembly language and all. 167 | 168 | The primary instructions: 169 | ``` 170 | *[a 0 b] /[b a] 171 | *[a 1 b] b 172 | *[a 2 b c] *[*[a b] *[a c]] 173 | *[a 3 b] ?*[a b] 174 | *[a 4 b] +*[a b] 175 | *[a 5 b] =*[a b] 176 | ``` 177 | 178 | We have our instructions for comparing two values, determining if a noun is a cell or just an atom, binary tree addressing, making a constant. 179 | 180 | Since it's homoiconic, so there's an `apply` instruction. 181 | 182 | And there's one – count 'em! – math instruction, increment. 183 | 184 | Now, as anyone who's read _The Little Schemer_ can verify, all arithmetic can ultimately be derived from the increment operation. It will be slow as hell, especially once you start doing division or matrix arithmetic, but it's possible. 185 | 186 | Here's how to decrement in Nock: 187 | ``` 188 | [8 [1 0] 8 [1 6 [5 [0 7] 4 0 6] [0 6] 9 2 [0 2] [4 0 6] 0 7] 9 2 0 1] 189 | ``` 190 | 191 | By having one math operation in Nock, we have explicitly taken speed and performance out of scope. This has to be solved at some point, but we're accepting it's not going to be solved here. 192 | 193 | ``` 194 | *[a 6 b c d] *[a 2 [0 1] 2 [1 c d] [1 0] 2 [1 2 3] [1 0] 4 4 b] 195 | *[a 7 b c] *[a 2 b 1 c] 196 | *[a 8 b c] *[a 7 [[7 [0 1] b] 0 1] c] 197 | *[a 9 b c] *[a 7 c 2 [0 1] 0 b] 198 | *[a 10 [b c] d] *[a 8 c 7 [0 3] d] 199 | *[a 10 b c] *[a c] 200 | *[a 12 [b c] d] #[b *[a c] *[a d]] 201 | 202 | ``` 203 | [//]: # (*) 204 | 205 | Instructions 6 through 12 are macros. We have our instructions for function composition, if-then-else, and editing a noun. 206 | 207 | And that's it. Those are all the instructions. And much like building all of math from increment, we build all of computing from these operations. 208 | 209 | There is some overlap with Lambda Calculus in its most stripped-down form, but this is not Lambda Calculus. 210 | 211 | ### Nock Tradeoffs 212 | 213 | Nock is not the simplest basis for computing. If it were, it wouldn't have macros, for starters. It's striving for the simplest basis of computing that is actually useful. 214 | 215 | We want macros, because we know we're going to have to do if-then-else, and it helps immensely if we know, at a glance, that this is what we're looking at, rather than having to infer it from a pile of comparisons and nested `apply` statements. 216 | 217 | The only primitive data type is an unsigned integer. Nock will treat all values like placeholders. What they _mean_ will be handled in Hoon. 218 | 219 | The only data structure is a binary tree. This is not the simplest data structure you could pick. Brainf\*ck uses a single array, which I would argue is way simpler. But if you're shooting for maximal power with minimal complexity, you really can't do better than a binary tree. 220 | 221 | On top of those, here's what else Nock does not have: 222 | 223 | - Variables 224 | - Functions 225 | - An environment 226 | - A syntax 227 | - Error handling of any kind 228 | 229 | ## Hoon 230 | 231 | Okay, with that background, we are finally in a position to look at Hoon. 232 | 233 | Hoon is the higher-level language of Urbit. It is statically typed and compiles down to Nock, which is then interpreted. 234 | 235 | Hoon is intended to be a systems language. It's designed to be good at compiling, running, and reloading programs at runtime. 236 | 237 | It can hot-reload an app, a kernel module, or the whole OS just by running a function on an incoming packet. 238 | 239 | The compiler for Hoon is written in Hoon. Please don't ask how this is 240 | possible. 241 | 242 | It also uses Kelvin versioning. Its version is 143 and it's still in active development, but it is intended to be frozen. 243 | 244 | So, like Hoon, it strives for simplicity, in its relationship to Nock, in what the compiler is doing, and in its semantics. 245 | 246 | ### Hoon's Terrifying Syntax 247 | 248 | Hoon programs are composed of `hoon`s, which are abstract syntax trees. 249 | 250 | Hoon expressions begin with a rune, which is a pair of ASCII characters, like `|=`. The runes are followed by one or more sub-expressions, which might be (and often are) Hoons themselves. 251 | 252 | Given the ASCII-heavy nature of Hoon, there's a handy set of nicknames for each ASCII character you might use. 253 | 254 | ``` 255 | ace [1 space] gal < pal ( 256 | bar | gap [>1 space, nl] par ) 257 | bas \ gar > sel [ 258 | buc $ hax # sem ; 259 | cab _ hep - ser ] 260 | cen % kel { sig ~ 261 | col : ker } soq ' 262 | com , ket ^ tar * 263 | doq " lus + tec ` 264 | dot . pam & tis = 265 | fas / pat @ wut ? 266 | zap ! 267 | ``` 268 | 269 | It's not entirely necessary to memorize this, but it will make it more likely your dreams will come true. 270 | 271 | Using these runes, you can actually speak Hoon code out loud. 272 | 273 | ### Demo App 274 | 275 | So, here's my favorite "Introduction to FP" example program, which takes some number `n` and prints the first `n` numbers in the Fibonacci sequence. 276 | 277 | ``` 278 | |= n/@ud :: 1 279 | ^- (list @ud) :: 2 280 | =/ a 0 :: 3 281 | =/ b 1 :: 4 282 | |- :: 5 283 | :- :: 6 284 | a :: 7 285 | ?: =(0 n) :: 8 286 | ~ :: 9 287 | $(n (dec n), a b, b (add a b)) :: 10 288 | ``` 289 | 290 | First we create a "gate", which is approximately a function (line 1). 291 | 292 | Line 2 casts the results to a list of unsigned decimals. 293 | 294 | We define a couple of variables (line 3, 4). 295 | 296 | Line 5 sets our recursion point (more or less). 297 | 298 | On line 6, `:-` or `colhep` means we're doing a `cons` to create a cell. The head of our cell, on line 7, is `a`. 299 | 300 | If our `n` is zero (line 8), we append a null to the list (line 9). The tilde, 301 | or `sig` is the null value in Hoon. 302 | 303 | Otherwise, we recurse with updated values (line 10). 304 | 305 | `n` is decremented, `a` is set to `b`, and `b` is the sum of the previous `a` and `b`. 306 | 307 | (Actually, the director's cut description of what's going on here is that line 4 creates a function named `$` (i.e. an anonymous function), whose contents are the rest of this program. This function also gets called immediately. 308 | 309 | Then on line 10, we call this function again, giving it a list of all the changes we've made.) 310 | 311 | ### Hoons 312 | 313 | Each rune in Hoon is expecting certain sub-expressions. Hoon eschews Lisp-style enclosing parentheses, so your code is mercifully free of terminator piles, like `)))))))))`. 314 | 315 | The tradeoff here is that you have to know what sub-expressions the compiler will be expecting, and you have to know where one Hoon ends and another one begins. 316 | 317 | You can generally clarify things through good use of style and indentation. 318 | 319 | As an example, if you need to create a cell, you use the `:-` rune. This is formally defined as: 320 | 321 | ``` 322 | {%clhp p=hoon q=hoon} 323 | ``` 324 | 325 | The `hoon`s in this definition, `p` and `q`, can be another value or the result of another Hoon. If you're making a cell of two values, you would write: 326 | 327 | ``` 328 | :- 42 420 329 | ``` 330 | 331 | Or these `hoon`s could be more complicated than that. In the case of our sample app, we `cons` the next Fibonacci number with the results of our `if` rune, (i.e. `?:` or `buccol`). 332 | 333 | ``` 334 | :- :: 6 335 | a :: 7 336 | ?: =(0 n) :: 8 337 | ~ :: 9 338 | $(n (dec n), a b, b (add a b)) :: 10 339 | ``` 340 | 341 | Other runes are usually used differently. One way to "declare a variable" is to use the `=/` or `tisfas` rune. 342 | 343 | In our sample app, we see this one used as: 344 | ``` 345 | =/ a 0 :: 3 346 | =/ b 1 :: 4 347 | |- :: 5 348 | ... 349 | ``` 350 | 351 | This rune is defined as: 352 | ``` 353 | {$tsfs p/toro q/hoon r/hoon} 354 | ``` 355 | 356 | (The type for `p` is `toro`, which is defined as a label and an optional type.) 357 | 358 | In our app on line 2, the `p` is `a`, our variable name. `q` is the value we're setting it to, i.e. zero. 359 | 360 | And what's `r`? `r`, in this case, is the rest of the program. 361 | 362 | You'll find that many runes are defined so that the last argument can be the rest of the program, such as the rune to cast to a type, `^-`, or to restrict the Hoon version, `!?`. 363 | 364 | These sorts of runes help give Hoon a procedural feel. In practice, Hoon is 100% a functional language, but you can lay your code out so that it reads like a sequence of actions. 365 | 366 | Maybe it's because I spent some of my crucial formative years doing turtle graphics, but this maps well to how my brain works. 367 | 368 | Well-styled Hoon should flow this way. If you have an if statement, `?:`, and you find that most of the "meat" happens when the test is `true`, use the inverted test, `?.`, so the `true` clause happens last. 369 | 370 | I think of style as "soft syntax". It's syntax that doesn't have to be formalized to the point that something as stupid as a compiler can handle it. It's syntax you can ignore if you have a good reason. And you can change style without having to push out a new version. 371 | 372 | ### The Subject 373 | 374 | Every operation is evaluated against one operand, the `subject`. It will return one result, the `product`. 375 | 376 | There's no "environment" or "scope". There's no symbol table. Just the subject. 377 | 378 | The subject, by default, contains the standard libraries, info about the ship we're running this on. But we can always add things to the subject, and explicitly choose a limited subset of the subject to work with. 379 | 380 | ### Digression #1 - Language Tradeoffs 381 | 382 | I described the syntax for Hoon as "terrifying", and I'm sticking with it. I think most Hoon developers would agree. 383 | 384 | Syntax here means, what do you have to type out to do the thing you're trying to do? How wordy and complicated is it? 385 | 386 | This is in contrast with semantics which means, what do the things you're typing out mean? Conceptually, how easily can this fit into your head? 387 | 388 | Another way to look at it is that syntax is the front-end, the thing the developer types in. Semantics is the back-end, what the machine is capable of doing with it? 389 | 390 | In any language, there's a tension between syntax and semantics. 391 | 392 | Some poorly designed languages can have complicated syntax and complicated semantics, but at some point, simplifying the one will come at the expense of making the other more complicated. 393 | 394 | A good example of this is static types vs. dynamic types. 395 | 396 | Dynamic types simplify the syntax for the simple reason that you don't have to specify a type. Something like this is perfectly valid in JavaScript: 397 | 398 | ``` 399 | thing = 23 400 | thing = "changed my mind" 401 | thing = 12.3 402 | ``` 403 | 404 | This comes at the expense of not exactly knowing what `thing` is. If I start using it in some other part of the code, what value is it going to have? Can I append a string to it? What's that going to look like? 405 | 406 | I'm picking on JavaScript because it's a [notorious semantic dumpster fire](https://www.destroyallsoftware.com/talks/wat). This is because the interpreter will accept pretty much anything as valid syntax, even things that don't make any sense semantically. 407 | 408 | Nock has very simple syntax and very simple semantics. There's nothing that's a big secret going on here. The problem is that, by itself, it's of extremely limited utility. You don't have to sweat how type inference works in Nock because Nock has no types. 409 | 410 | Which brings up the third point. Expressiveness. What concepts do the language make possible? How easy is it to put these ideas into code? 411 | 412 | So I've made a handy diagram to condense this idea. This is the most important 413 | diagram in this article. I present: 414 | 415 | ## LeBlanc's Triangle 416 | 417 |  418 | 419 | Please note: this idea is not particularly original. What is original is making it into a triangle shape and naming it after myself. 420 | 421 | When you give something a triangle shape, you're saying, “Pick any two”. 422 | 423 | Hoon has very complicated syntax but the semantics are actually fairly straightforward. There's no magic happening inside the compiler. If you understand the code, you pretty much understand what the compiler is doing. 424 | 425 | However, even though the semantics are simple, they're chosen in such a way that you can express some really powerful ideas with them. 426 | 427 | ## Types 428 | 429 | I would dearly love to run you through all the ins and outs of Hoon, but they gave me 50 minutes for a talk, and the above content used up a ton of them. 430 | 431 | So for the remainder, I'd like to drill a bit into types and how they work in Hoon. 432 | 433 | Hoon is a strongly-typed language, but its type system works completely differently, and way more simply, than most other strongly-typed languages. 434 | 435 | Hopefully you can get a flavor of how close we can get to the fully-armed and operational bidirectional type inference system which Hindley-Milner makes possible, only with 1/1000th the complexity. 436 | 437 | ### Molds 438 | 439 | So, let's talk about what a type actually is. A type is a set of values which conforms to some kind of semantic criteria. It could have infinitely many possible values or it could have zero possible values. Or one possible value. 440 | 441 | (Side note: what's the difference between a type with one value and a constant? 442 | A: There isn't any.) 443 | 444 | Conveniently enough, this definition is really close to the definition of a function. A function takes as input some domain, does stuff, and its output is some other domain, the range. All functional languages work like this, and Hoon is a functional language. 445 | 446 | So for a minimalist type system, all you really need is the humble function. You could take as the domain any value and validate that it conforms to your semantic criteria. If it does, pass it along as-is. If it doesn't, pass along some default value that's still part of your type domain. 447 | 448 | Not at all coincidentally, this is how a `mold` works in Hoon, as a type constructor function. 449 | 450 | ### Atoms 451 | 452 | As a thin layer over Nock, Hoon continues to use the `noun` as its one way of representing a value. This certainly makes the type-constructor-as-function thing quite a bit easier, since the possible values we might want types for are numbers and binary trees of numbers. 453 | 454 | Hoon adds a few additional bits of decoration and functionality to make everyone's life easier. 455 | 456 | In Nock, we represented all data contents as unsigned integers, which we said was pretty useless but we'd deal with it in the future. Well, the future is now. 457 | 458 | So, an arbitrary-length unsigned integer is basically a collection of bytes. So really, anything that can be represented as bytes can be an atom: 459 | 460 | - Signed ints 461 | - Unsigned ints 462 | - Floats 463 | - Strings 464 | - Bitcoin wallet 465 | - Animated gifs 466 | - The genome for the naked molerat 467 | - A cracked copy of Duke Nukem II 468 | 469 | If we know what our atom represents, we can assign a soft type, or an `aura` to it. All but the last three on this list are valid atoms, by the way, including the bitcoin wallet. 470 | 471 | Auras specialize to the right: 472 | ``` 473 | @ => any atom 474 | @t => UTF-8 text (a `cord`) 475 | @ta => ASCII text 476 | @tas => ASCII text symbol (lowercase letters and digits only) 477 | ``` 478 | 479 | Auras are truly soft types. Underneath it all, they're still just integers, and you can convert any aura to any other aura, if you first convert it to `@`. 480 | 481 | ``` 482 | @c UTF-32 ~-foobar 483 | @da 128-bit absolute date ~2016.4.23..20.09.26..f27b..dead..beef..babe 484 | ~2016.4.23 485 | @dr 128-bit relative date ~s17 (17 seconds) 486 | ~m20 (20 minutes) 487 | ~d42 (42 days) 488 | @f loobean & (0, yes) 489 | | (1, no) 490 | @p ~zod (0) 491 | @rs 32-bit IEEE float .3.14 (pi) 492 | .-3.14 (negative pi) 493 | @sd signed decimal --2 (2) 494 | -5 (-5) 495 | @ub unsigned binary 0b10 (2) 496 | @uc bitcoin address 0c1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa 497 | @ud unsigned decimal 42 (42) 498 | 1.420 (1420) 499 | @ux unsigned hexadecimal 0xcafe.babe 500 | ``` 501 | _A non-exhaustive list of auras and their associated literals._ 502 | 503 | The syntax for our data literals is a bit complicated for two reasons: 504 | 1. They're designed to be URL-safe 505 | 2. They all have different parse rules 506 | 507 | This is an example of the tradeoff between syntax and semantics. Once you know the rules for what a float looks like — `=/ pi .3.14159` — our understanding of what kind of this number this is is exactly the same as the compiler's. 508 | 509 | One `aura` that is unique to Urbit is `@p`, which is the phonemic base. This is a pronounceable, base-256 representation: 510 | 511 | ``` 512 | ~leb => 145 513 | ~samtul => 1066 514 | ``` 515 | 516 | So, let's take these auras for a spin. 517 | 518 | ``` 519 | > (add 70 4) 520 | 74 521 | > ? (add 70 4) 522 | @ 523 | 74 524 | ``` 525 | If we use `wut`, we can get the return type for this function, which is `@` or "any atom". 526 | 527 | ``` 528 | > ^- @t (add 70 4) 529 | 'J' 530 | > ^- @ 'J' 531 | 74 532 | ``` 533 | We can use `^-` or `kethep` to cast the return value to `@t` or "text". We can also go the other direction and cast 'J' back into a generic atom. 534 | 535 | ``` 536 | > ^- @t 74 537 | /~ribben-donnyl/home/~2018.5.27..17.31.49..d55f/sys/vane/ford:<[1.290 24].[1.290 52]> 538 | nest-fail 539 | \/ford: build failed ~[/g/~ribben-donnyl/use/dojo/~ribben-donnyl/inn/hand 540 | /g/~ribben-donnyl/use/hood/~ribben-donnyl/out/dojo/drum/phat/~mall\/ 541 | et-rilmul/dojo /d //term/1] 542 | \/ 543 | > ? 74 544 | @ud 545 | 74 546 | ``` 547 | If we try to cast 74 directly to text, we get a nest-fail. When we call `wut` to see what our type is, it's `@ud`, an unsigned decimal. 548 | 549 | So you can make auras more specific or less specific, but you can't alter their type directly. You can't convert from the "unsigned" type `@u` to the "text" type `@t`. 550 | 551 | ``` 552 | > `@t``@`74 553 | 'J' 554 | ``` 555 | The shorthand for casting is to use `tec`s, i.e. backticks. We can do this conversion if we cast the `@ud` to `@` or "any atom" first, and then cast to `@t`. 556 | 557 | ``` 558 | > `@p``@`.127.0.0.1 559 | ~bidpur-hidpex 560 | ``` 561 | Here's our phonemic base in action. All the addresses for personal IDs in Urbit are the size of an IP address. 562 | 563 | Here's my personal ship cast to an IP address: 564 | ``` 565 | > `@if`~ribben-donnyl 566 | .33.165.1.0 567 | ``` 568 | `@if` is "IP v4 address". I think the pronounceable version is more memorable, but your opinion might vary. 569 | 570 | Finally, here's a look at signed numbers. 571 | ``` 572 | > `@`--51 573 | 102 574 | > `@`-51 575 | 101 576 | ``` 577 | 578 | Atoms are arbitrary-length, you can't do the usual two's complement, since there's no way of knowing how many bytes you'll need. Instead, the sign bit is the lowest bit. Positive `n` is `n*2`, negative `n` is `n*2-1`. 579 | 580 | ### Faces 581 | 582 | In Nock, we have one operation to extract data from our binary tree, which is to give it an address. 583 | 584 | 1 is the address for the whole tree, 2 is the address for the left subtree, 3 is for the right subtree, etc. 585 | 586 |  587 | 588 | In Hoon, we can add label these nodes, which are called "surfaces" or just "faces". To get some node or subtree out of a noun, you give it a face and Hoon performs a depth-first search and returns the first element it finds. 589 | 590 | Using these faces, you can make your noun work like an associative array, or dictionary, or hash table, or whatever your language calls them. 591 | 592 |  593 | 594 | There is no symbol table in Hoon. Just a depth-first search. 595 | 596 | ### Unions 597 | 598 | Hoon has the equivalent of the `Either` type, only better! You are not limited to a `Left` and `Right`. You can declare a type that is a union of an arbitrary number of other types. 599 | 600 | One of these union types is `unit`, which works like a `Maybe`, or an `Option`, or a `Possibly`. 601 | 602 | This has two acceptable values, either the null value, `$~`, which means `Nothing`, or `None`. Then there's `[~ a]`, which means either `Just a` or `Some a`. 603 | 604 | And in case anyone is wondering, the Hoon standard library supplies the axiomatic Monadic functions, `bind` and `just`, which Haskell calls `return`. 605 | 606 | ### Typechecking 607 | 608 | In Urbit, typechecking is called `nest`-ing. So, a type is a set of values. And a type check verifies that this type's set of values "nests" inside some other type's set of values? Is it a subset? 609 | 610 | Say we want a type to represent a dog. I mentioned we can use faces and make an associative array. So let's do that. 611 | 612 | ``` 613 | > =dog {name/@t color/@ta date-of-birth/@da} 614 | ``` 615 | 616 | Urbit comes with a built-in Hoon REPL. It's got some non-standard features, 617 | like this `=dog`, which creates a variable called `dog`. And `dog` is a type, 618 | with three labeled attributes, a `name`, which is any UTF-8 text, a `color`, 619 | which is limited to ASCII, and date of birth, which is an absolute date. 620 | 621 | ``` 622 | > =bruno [name='Bruno🐶' color='faun' date-of-birth=~2016.12.10] 623 | > ? bruno 624 | {name/@t color/@t date-of-birth/@da} 625 | [name='Bruno🐶' color='faun' date-of-birth=~2016.12.10] 626 | ``` 627 | We make another variable called `bruno`. 628 | 629 | Using the `?` or `wut` here, we can verify Bruno's type. We didn't declare Bruno with any reference to our `dog` type, but he's got the same tree "shape", faces, and compatible auras. 630 | 631 | ``` 632 | > ?? bruno 633 | 634 | [ %cell 635 | [%face [~ %name] [%atom %t ~]] 636 | [ %cell 637 | [%face [~ %color] [%atom %ta ~]] 638 | [%face [~ %date-of-birth] [%atom %da ~]] 639 | ] 640 | ] 641 | [name='Bruno🐶' color=~.faun date-of-birth=~2016.12.10] 642 | ``` 643 | 644 | We can all do a `wutwut`, to see Bruno's uncensored type, its type as a noun. 645 | 646 | Internally, this type is composed of several subtypes, for cells, faces, atoms, etc. 647 | 648 | ``` 649 | > `dog`bruno 650 | [name='Bruno🐶' color=~.faun age=~2016.12.10] 651 | ``` 652 | We attempt to cast bruno as a dog. Bruno is, in fact, a valid dog. 653 | 654 | Let's make another dog, called `rex`, with a completely invalid date of birth. 655 | 656 | ``` 657 | > =rex [name='rex' color='hairless' dob=42] 658 | > `dog`rex 659 | 660 | /~ribben-donnyl/home/~2018.5.27..17.31.49..d55f/sys/vane/ford 661 | <[1.290 24].[1.290 52]> 662 | nest-fail 663 | \/ford: build failed ~[/g/~ribben-donnyl/use/dojo/~ribben-donnyl/inn/hand /g/\/ 664 | ~ribben-donnyl/use/hood/~ribben-donnyl/out/dojo/drum/phat/~ribben-donnyl/do 665 | jo /d //term/1] 666 | \/ 667 | ``` 668 | When we try to cast `rex` as a dog, we get a `nest-fail`. `rex` is a bad dog. 669 | 670 | I mentioned that molds are functions. You can actually call them and provide them with a sample. If the sample is of the correct type, it will return the sample. If it isn't, it will return a default value. 671 | 672 | You don't usually do this directly. Usually the compiler takes care of types for you. But you can if you want to validate untrusted network traffic, for instance. 673 | 674 | So let's say we have a service that accepts a dog as input. 675 | 676 | ``` 677 | > (dog bruno) 678 | [name='Bruno🐶' color=~.faun date-of-birth=~2016.12.10] 679 | ``` 680 | We call `dog` with `bruno`, and we get back `bruno`. 681 | 682 | ``` 683 | > (dog 17) 684 | [name='' color=~. date-of-birth=~292277024401-.1.1] 685 | ``` 686 | 687 | We call `dog` with a silly value, we get back a dog with all its values filled 688 | with defaults. 689 | 690 | So the mold doesn't necessarily give you back the value that was sent over the wire. But it doesn't give you something malformed or malicious either. 691 | 692 | ``` 693 | > (dog rex) 694 | [ name='rex' 695 | color=~.hairless 696 | age=~292277024401-.1.1..00.00.00..0000.0000.0000.002a 697 | ] 698 | ``` 699 | 700 | If we call it with `rex`, it returns a valid `dog` and it converts `rex`'s weird date of birth to a valid absolute date. 701 | 702 | Since nouns in Hoon are atoms, trees, and node labels, for the developer, figuring out the "type" of a value is often a matter of pattern matching. In Hoon this uses the `?=` or `wuttis` rune. 703 | ``` 704 | > ?=({color/@ *} bruno) 705 | %.y 706 | ``` 707 | In this instance, we're just asking if `bruno`'s type has a node labeled `color`, which contains an atom. The `*` means "any noun" so we want `bruno` to have any other value to pattern match. 708 | 709 | And `%.y` means this matches. 710 | 711 | ``` 712 | > ?=({color/* @ @ @} bruno) 713 | %.n 714 | ``` 715 | If we asked if `bruno` has color and three more nodes, this fails. 716 | 717 | Let's make a `cat` type. 718 | ``` 719 | > =cat {color/@ta date-of-birth/@da} 720 | ``` 721 | Unlike the `dog`, it's got two values, a color and a date of birth. (It has no name, because it's not like if you call its name, a cat is going to come to you.) 722 | 723 | ``` 724 | > =pet $?(dog cat) 725 | ``` 726 | Now using `$?`, or `buckwut`, we make a union type, a `pet`, which is either a `cat` or a `dog`. 727 | 728 | ``` 729 | > `pet`bruno 730 | [name='Bruno🐶' color=~.faun date-of-birth=~2016.12.10] 731 | ``` 732 | Then we try to cast `bruno` as a `pet` and it works. 733 | 734 | ### Cores 735 | 736 | I've thrown out the word "function" many times, as you'd expect for a conference on functional programming. However, the details are a bit more complicated than you might assume from the term "function". 737 | 738 | Functions in Hoon appears inside a `core`, which is a cell with two parts: the code and the data. The code takes the form of a list of compiled attributes, called `arm`s. 739 | 740 | A `gate` is a special case, being a core with one nameless arm, i.e. a lambda. 741 | 742 | This is a recurring pattern in Hoon. If you can make one of something, you can also make a whole list of something, and the one-of-something is a special case. 743 | 744 | Cores serve the same function as objects in most other languages, since they contain both methods and data. 745 | 746 | (Side note: "Nameless Arm" would make a pretty good album name) 747 | 748 | ## Mint 749 | 750 | Types are only really important within the context of a function. What sorts of values can this function handle as input, and what sort of values is it going to return? 751 | 752 | In Hoon, you do not declare a type. They're only inferred by the compiler. You pass code to the compiler and it returns a pair of a type and Nock code. 753 | 754 | Type inference in Hoon only happens forwards, not backwards. Which means the compiler will determine the return value of a function, but won't figure out the types of the function arguments. 755 | 756 | This means that, since you can't rely on the compiler to figure out all your types, you have to annotate them. Although this arguably makes your code more readable. 757 | 758 | Sometimes, you might choose to annotate where it isn't technically needed. Looking at our Fibonacci program from above: 759 | ``` 760 | |= n/@ud :: 1 761 | ^- (list @ud) :: 2 762 | =/ a 0 :: 3 763 | =/ b 1 :: 4 764 | |- :: 5 765 | :- :: 6 766 | a :: 7 767 | ?: =(0 n) :: 8 768 | ~ :: 9 769 | $(n (dec n), a b, b (add a b)) :: 10 770 | ``` 771 | 772 | On line 2, we cast the results of everything that follows to a list of unsigned decimals. Since this is the first line in this function, this will end up the inferred type for this whole program. 773 | 774 | This does a few things: 775 | 776 | 1. It documents exactly what we're trying to do here. 777 | 2. If this isn't giving us the type we think it should, we'll get a type error, a `nest-fail`, right there at line 2, rather than somewhere else in the app. 778 | 779 | This applies to pretty much any language. Just because the compiler will infer what you're doing, unless you're doing something explicitly generic, it won't kill you to type out what you're expecting and can save you some trouble. 780 | 781 | As it happens, if you leave out the cast, the inferred type is almost a list of unsigned decimals... but not exactly. The compiler tends to err toward being too generic and it can drop information you might be expecting. 782 | 783 | You can end up with really subtle bugs if you just let the compiler do all the work for you. 784 | 785 | So here's a function to make a tuple, an arbitrary-length list: 786 | ``` 787 | > :+(42 420 %gocards) 788 | [42 420 %gocards] 789 | ``` 790 | 791 | We can call the parser from the command line: 792 | ``` 793 | > =parsed (ream ':+(42 420 %gocards)') 794 | > parsed 795 | [%clls p=[%sand p=%ud q=42] q=[%sand p=%ud q=420] r=[%rock p=%tas q=32.480.064.744.681.319]] 796 | ``` 797 | 798 | Then we can pass this parsed expression to the compiler: 799 | ``` 800 | > (~(mint ut %noun) %noun parsed) 801 | [#t/{@ud @ud $gocards} q=[%1 p=[42 420 32.480.064.744.681.319]]] 802 | ``` 803 | 804 | The `q` is our compiled Nock code. And the `#t` is our type, which says, as we were expecting, it's a tuple with two unsigned integers and a constant, gocards. 805 | 806 | (Go Cards!) 807 | 808 | ### Polymorphism 809 | 810 | We don't want to have to write a different function for every type if we don't 811 | have to, and we don't have to in Hoon through the magic of polymorphism. 812 | 813 | What we've been describing is, in Hoon terms, "dry polymorphism", or "variance". This uses Liskov substitution, which is the same type checking we were using above when we were talking about auras and dogs. 814 | 815 | However, we're leaving a whole lot of tasty functional goodness if we don't talk about the other sort of polymorphism, "wet polymorphism" or "genericity". 816 | 817 | When we use wet gates, our code is compiled, but we defer doing our full type inference until we actually use the function. Then we infer the type based on the inputs we are given. So the function acts something like a macro. 818 | 819 | So, here's a very simple function that will work for pedagogic purposes. 820 | ``` 821 | |= p/(list *) :: 1 822 | |- :: 2 823 | ?~ p :: 3 824 | ~ :: 4 825 | :- i=i.p :: 5 826 | t=$(p t.p) :: 6 827 | ``` 828 | 829 | This program accepts a list and recursively rebuilds the same list. 830 | 831 | ``` 832 | > `(list *)`(limo ~[1 2 3]) 833 | ~[1 2 3] 834 | ``` 835 | `limo` will take a null-terminated tuple and make a `list` out of it. 836 | 837 | ``` 838 | > +rebuild (limo ~[1 2 3]) 839 | ~[1 2 3] 840 | ``` 841 | 842 | So far so good. Now, what if we use a `tape`, which is a string represented as a list of characters? 843 | 844 | ``` 845 | > +rebuild "tape" 846 | ~[116 97 112 101] 847 | ``` 848 | 849 | This is not what we wanted! 850 | 851 | The problem is, when this compiled as a dry gate, it set the type of the `list` to `*`, which is "any noun". Then when we call it, it casts the characters in our string to their raw ints and that's what we build a list out of. 852 | 853 | ``` 854 | |* p/(list) :: 1 855 | ``` 856 | 857 | So we change our `|=`, which makes a dry gate, to a `|*`, which makes a wet gate. 858 | 859 | ``` 860 | > +rebuild "tape" 861 | "tape" 862 | ``` 863 | And when we run it, we get our string back. 864 | 865 | The standard library uses wet gates for things like folds, maps, and the sorts of things you might use a typeclass for in Haskell. 866 | 867 | ## And That's All for Now 868 | 869 | At this point, I would dearly love to launch into a discussion on how cores work, some of the more interesting details on type inference, and the menu of options for polymorphism open to you. It's got a much more extensive menu of options so much more than wet vs. dry. 870 | 871 | However, that's all we have time for. However, I will be happy to discuss cores, type inference, and polymorphism with everyone at LambdaConf 2019. 872 | 873 | ## Conclusions 874 | 875 | So, here's what I'm hoping everyone takes away from this, even if they never 876 | look at anything Urbit-related ever again: 877 | 878 | - The amazing possibility of computing based on nothing but unsigned integers, binary trees, and increment. 879 | 880 | - ASCII Pronunciation 881 | 882 | Give it a try! You'll find coding to be a different experience when you can 883 | subvocalize what you're typing out. 884 | 885 | - Kelvin Versioning 886 | 887 | I think Kelvin versioning would work well with a REST endpoint that's under development. You might have some other developers who are also developing a client to consume your data, and giving your return value a Kelvin version lets them know how close you are to being "done" and if something has changed. 888 | 889 | - LeBlanc's Triangle 890 | 891 |  892 | 893 | I'm serious about that one. Spread the word on that. Work it into conversation with your friends and in-laws. Don't forget to call it "LeBlanc's Triangle". 894 | 895 | If that takes off, you can be a total hipster about it and say, "Yeah, I was there at the conference when the eponymous LeBlanc introduced it." 896 | 897 | Really, everyone's a winner on that. 898 | 899 | And finally, to quote the Urbit whitepaper: 900 | 901 | > _"Urbit is cool and you should check it out."_ 902 | 903 | This is a good time for that. After this latest version of Hoon, development is being transitioned out of "auteur" mode and handed over to the developer community. 904 | 905 | Hoon makes no use of category theory. Should it? What are we missing without it? Get involved and make the system better. 906 | 907 | ## Some Links 908 | 909 | [Install Urbit](https://urbit.org/docs/using/install/) and have a look. 910 | 911 | Then [check out the stream](https://urbit.org/stream/). You can chat with people, possibly myself, and pepper everyone with questions. 912 | 913 | If you see `~ribben-donnyl` on there, that's me. Say hello! 914 | 915 | Also, you might have seen me walking around with a variety of cool Urbit shirts. 916 | [They're available for purchase.](http://urbit.threadless.com) 917 | 918 | ## Acknowledgments 919 | 920 | - Ted, Mark, and everyone at Tlon 921 | - Josh Reagan at Rice 922 | - WWT Asynchrony Labs - [We're hiring](https://www2.wwt.com/careers/)! Openings in St. Louis and Denver. 923 | 924 | {% endraw %} 925 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 |
Delivered as a talk, LambdaConf 2018
4 |This article is about the programming language Hoon, its philosophical and technological underpinnings.
5 |When you finish reading this, you will probably not be able to do anything in Hoon that you didn’t already walk in here knowing.
6 |But Hoon is completely unlike any other functional programming language you might know, and so we can use it to take a look at familiar concepts from a completely different angle. Things like types.
7 |Along the way, we’ll look at programming language design and what kinds of tradeoffs go into it.
8 |There are a lot of really smart and talented people involved in this project. I am not one of them. But I’ve had to dumb down the material to the point that I was able to understand it, and hopefully you’ll be able to profit from that effort.
9 |So, let’s get to it, by first talking about what Hoon is for.
10 |Much as C was developed to write Unix, Hoon was developed to write Urbit. So we should first mention in passing what this Urbit thing is.
12 |Urbit is the somewhat ambitious project of condemning all computing as we know it to complete obsolescence. It’s a clean-slate system software stack, which includes the following:
13 |And when I say “clean-slate”, the operating metaphor has been to imagine they find a spaceship from Mars crash-landed outside of Bozeman, MT. What kind of software would they be running? (Spoiler: probably not Windows ME.)
23 |There’s not a single assumption about how computer systems should work that has not been revisited.
24 |If you stop occasionally and ask, “Why did they do it that way?”, it’s a very rewarding project to study. Although this can lead you into a rabbit hole.
25 |For instance, they use something called “Kelvin versioning”. Instead of versions increasing, they decrease, toward zero. They use this for Hoon as well as Nock, the lowest level language, which we’ll discuss shortly.
27 |Why is this? Why not just increment versions like everyone else does?
28 |The answer is, because software improvement is usually considered an additive process. Version 2.0 does more stuff than version 1.0, version 2.1 is a lot like version 2.0, except it works better. We can assume there will be a version 3.0 sometime in the future which will work differently in some fundamental way, hopefully better but often not.
29 |Usually this is sensible. We launch software in an environment of uncertainty.
30 |As time goes on, we understand the problem space better, our users gain some sophistication, technology changes, so we refine the software to match our better understanding.
31 |Or sometimes decisions come straight from marketing.
32 |33 |35 |CloudTron v4 - Now with Blockchain! - Add a rich, smoky bacon scent to all your cloud-stored files.
34 |
But why do we assume that progress means change?
37 |Take JSON. JSON is designed for packaging data as text to send between clients and servers. And by design, it has barely changed since the moment it came out.
38 |I can imagine my great-grandchildren using JSON that looks exactly like the JSON I use. Which means, if I need to send out data from a server I’m writing, if I use JSON, whatever else I might have to change or update or fix, the actual format the data goes out in will not change at all.
39 |So maybe I might want to do things differently. Personally, I’d allow an optional trailing comma at the end of arrays.
40 |But any minor improvements you might now make to JSON have to be weighed against the immense convenience of having this entire part of your code that never has to change.
41 |Whatever else you might need in CloudTron v.5, it won’t need to support a new version of JSON.
42 |And that’s just the problem of formatting data as text.
43 |Now imagine the language, the libraries, the network stack, the entire OS never changes. Imagine never being in dependency hell because everything you’re working with is effectively “done” except this app you just had an idea for.
44 |So to pull ourselves out of this rabbit-hole, maybe sometimes the ideal solution to a problem is software that stops changing. You define its scope as something achievable, and “progress” means decreasing the number of things that have to change, because everything else is working like it’s supposed to.
45 |In other words, it’s being frozen. Hence, Kelvin versioning. We assume some Platonic ideal solution, set that version to 0K, and our versions are getting ever closer this ideal.
46 |To make a software system that can be frozen, it has to be simple.
48 |You have to be ruthless in your pursuit of simplicity. You have to make hard and fast choices about what is or isn’t in scope. If it’s not in scope, do the absolute minimum you can with it.
49 |This way, once you do tackle this bit of functionality, you’ll have the most options available to you.
50 |This is a solution to the Law of Leaky Abstractions:
51 |52 |54 |If there’s no functionality to abstract away, there’s nothing to leak.
53 |
Ultimately, you can build something simple from something else that’s simple. You cannot build something simple from something complex.
55 |The best you can hope for is to find a way to shift some of the complexity away from some users, at the cost of making things vastly more complicated for other users.
56 |Maybe you’ll end up with a simple, elegant front-end and a nightmarish back-end. Or vice versa.
57 |So what is the simplest basis for a computing system that can be frozen?
59 |The Urbit answer for this is Nock. Here’ is Nock 4K. Functional assembly language:
60 |A noun is an atom or a cell. An atom is a natural number. A cell is an ordered pair of nouns.
61 |
62 | nock(a) *a
63 | [a b c] [a [b c]]
64 |
65 | ?[a b] 0
66 | ?a 1
67 | +[a b] +[a b]
68 | +a 1 + a
69 | =[a a] 0
70 | =[a b] 1
71 | =a =a
72 |
73 | /[1 a] a
74 | /[2 a b] a
75 | /[3 a b] b
76 | /[(a + a) b] /[2 /[a b]]
77 | /[(a + a + 1) b] /[3 /[a b]]
78 | /a /a
79 |
80 | #[1 a b] a
81 | #[(a + a) b c] #[a [b /[(a + a + 1) c]] c]
82 | #[(a + a + 1) b c] #[a [/[(a + a) c] b] c]
83 | #a #a
84 |
85 | *[a [b c] d] [*[a b c] *[a d]]
86 |
87 | *[a 0 b] /[b a]
88 | *[a 1 b] b
89 | *[a 2 b c] *[*[a b] *[a c]]
90 | *[a 3 b] ?*[a b]
91 | *[a 4 b] +*[a b]
92 | *[a 5 b] =*[a b]
93 |
94 | *[a 6 b c d] *[a 2 [0 1] 2 [1 c d] [1 0] 2 [1 2 3] [1 0] 4 4 b]
95 | *[a 7 b c] *[a 2 b 1 c]
96 | *[a 8 b c] *[a 7 [[7 [0 1] b] 0 1] c]
97 | *[a 9 b c] *[a 7 c 2 [0 1] 0 b]
98 | *[a 10 [b c] d] *[a 8 c 7 [0 3] d]
99 | *[a 10 b c] *[a c]
100 | *[a 12 [b c] d] #[b *[a c] *[a d]]
101 |
102 | *a *a
103 | It is very, very simple.
104 |The data model is:
105 |A noun is an atom or a cell. An atom is any natural number.
106 | A cell is any ordered pair of nouns.
107 | Basically a noun is a binary tree whose leaves are numbers, unsigned integers of arbitrary length. Someone described the noun as an “S-Expression without the S”, since Nock takes no interest in what the atom is.
108 |nock(a) *a
109 | This line notes that nock is a function, which makes sense, this being functional assembly language and all.
110 |The primary instructions:
111 |*[a 0 b] /[b a]
112 | *[a 1 b] b
113 | *[a 2 b c] *[*[a b] *[a c]]
114 | *[a 3 b] ?*[a b]
115 | *[a 4 b] +*[a b]
116 | *[a 5 b] =*[a b]
117 | We have our instructions for comparing two values, determining if a noun is a cell or just an atom, binary tree addressing, making a constant.
118 |Since it’s homoiconic, so there’s an apply instruction.
And there’s one – count ’em! – math instruction, increment.
120 |Now, as anyone who’s read The Little Schemer can verify, all arithmetic can ultimately be derived from the increment operation. It will be slow as hell, especially once you start doing division or matrix arithmetic, but it’s possible.
121 |Here’s how to decrement in Nock:
122 |[8 [1 0] 8 [1 6 [5 [0 7] 4 0 6] [0 6] 9 2 [0 2] [4 0 6] 0 7] 9 2 0 1]
123 | By having one math operation in Nock, we have explicitly taken speed and performance out of scope. This has to be solved at some point, but we’re accepting it’s not going to be solved here.
124 |*[a 6 b c d] *[a 2 [0 1] 2 [1 c d] [1 0] 2 [1 2 3] [1 0] 4 4 b]
125 | *[a 7 b c] *[a 2 b 1 c]
126 | *[a 8 b c] *[a 7 [[7 [0 1] b] 0 1] c]
127 | *[a 9 b c] *[a 7 c 2 [0 1] 0 b]
128 | *[a 10 [b c] d] *[a 8 c 7 [0 3] d]
129 | *[a 10 b c] *[a c]
130 | *[a 12 [b c] d] #[b *[a c] *[a d]]
131 |
132 | Instructions 6 through 12 are macros. We have our instructions for function composition, if-then-else, and editing a noun.
133 |And that’s it. Those are all the instructions. And much like building all of math from increment, we build all of computing from these operations.
134 |There is some overlap with Lambda Calculus in its most stripped-down form, but this is not Lambda Calculus.
135 |Nock is not the simplest basis for computing. If it were, it wouldn’t have macros, for starters. It’s striving for the simplest basis of computing that is actually useful.
137 |We want macros, because we know we’re going to have to do if-then-else, and it helps immensely if we know, at a glance, that this is what we’re looking at, rather than having to infer it from a pile of comparisons and nested apply statements.
The only primitive data type is an unsigned integer. Nock will treat all values like placeholders. What they mean will be handled in Hoon.
139 |The only data structure is a binary tree. This is not the simplest data structure you could pick. Brainf*ck uses a single array, which I would argue is way simpler. But if you’re shooting for maximal power with minimal complexity, you really can’t do better than a binary tree.
140 |On top of those, here’s what else Nock does not have:
141 |Okay, with that background, we are finally in a position to look at Hoon.
150 |Hoon is the higher-level language of Urbit. It is statically typed and compiles down to Nock, which is then interpreted.
151 |Hoon is intended to be a systems language. It’s designed to be good at compiling, running, and reloading programs at runtime.
152 |It can hot-reload an app, a kernel module, or the whole OS just by running a function on an incoming packet.
153 |The compiler for Hoon is written in Hoon. Please don’t ask how this is possible.
154 |It also uses Kelvin versioning. Its version is 143 and it’s still in active development, but it is intended to be frozen.
155 |So, like Hoon, it strives for simplicity, in its relationship to Nock, in what the compiler is doing, and in its semantics.
156 |Hoon programs are composed of hoons, which are abstract syntax trees.
Hoon expressions begin with a rune, which is a pair of ASCII characters, like |=. The runes are followed by one or more sub-expressions, which might be (and often are) Hoons themselves.
Given the ASCII-heavy nature of Hoon, there’s a handy set of nicknames for each ASCII character you might use.
160 |ace [1 space] gal < pal (
161 | bar | gap [>1 space, nl] par )
162 | bas \ gar > sel [
163 | buc $ hax # sem ;
164 | cab _ hep - ser ]
165 | cen % kel { sig ~
166 | col : ker } soq '
167 | com , ket ^ tar *
168 | doq " lus + tec `
169 | dot . pam & tis =
170 | fas / pat @ wut ?
171 | zap !
172 | It’s not entirely necessary to memorize this, but it will make it more likely your dreams will come true.
173 |Using these runes, you can actually speak Hoon code out loud.
174 |So, here’s my favorite “Introduction to FP” example program, which takes some number n and prints the first n numbers in the Fibonacci sequence.
|= n/@ud :: 1
177 | ^- (list @ud) :: 2
178 | =/ a 0 :: 3
179 | =/ b 1 :: 4
180 | |- :: 5
181 | :- :: 6
182 | a :: 7
183 | ?: =(0 n) :: 8
184 | ~ :: 9
185 | $(n (dec n), a b, b (add a b)) :: 10
186 | First we create a “gate”, which is approximately a function (line 1).
187 |Line 2 casts the results to a list of unsigned decimals.
188 |We define a couple of variables (line 3, 4).
189 |Line 5 sets our recursion point (more or less).
190 |On line 6, :- or colhep means we’re doing a cons to create a cell. The head of our cell, on line 7, is a.
If our n is zero (line 8), we append a null to the list (line 9). The tilde, or sig is the null value in Hoon.
Otherwise, we recurse with updated values (line 10).
193 |n is decremented, a is set to b, and b is the sum of the previous a and b.
(Actually, the director’s cut description of what’s going on here is that line 4 creates a function named $ (i.e. an anonymous function), whose contents are the rest of this program. This function also gets called immediately.
Then on line 10, we call this function again, giving it a list of all the changes we’ve made.)
196 |Each rune in Hoon is expecting certain sub-expressions. Hoon eschews Lisp-style enclosing parentheses, so your code is mercifully free of terminator piles, like ))))))))).
The tradeoff here is that you have to know what sub-expressions the compiler will be expecting, and you have to know where one Hoon ends and another one begins.
199 |You can generally clarify things through good use of style and indentation.
200 |As an example, if you need to create a cell, you use the :- rune. This is formally defined as:
{%clhp p=hoon q=hoon}
202 | The hoons in this definition, p and q, can be another value or the result of another Hoon. If you’re making a cell of two values, you would write:
:- 42 420
204 | Or these hoons could be more complicated than that. In the case of our sample app, we cons the next Fibonacci number with the results of our if rune, (i.e. ?: or buccol).
:- :: 6
206 | a :: 7
207 | ?: =(0 n) :: 8
208 | ~ :: 9
209 | $(n (dec n), a b, b (add a b)) :: 10
210 | Other runes are usually used differently. One way to “declare a variable” is to use the =/ or tisfas rune.
In our sample app, we see this one used as:
212 |=/ a 0 :: 3
213 | =/ b 1 :: 4
214 | |- :: 5
215 | ...
216 | This rune is defined as:
217 |{$tsfs p/toro q/hoon r/hoon}
218 | (The type for p is toro, which is defined as a label and an optional type.)
In our app on line 2, the p is a, our variable name. q is the value we’re setting it to, i.e. zero.
And what’s r? r, in this case, is the rest of the program.
You’ll find that many runes are defined so that the last argument can be the rest of the program, such as the rune to cast to a type, ^-, or to restrict the Hoon version, !?.
These sorts of runes help give Hoon a procedural feel. In practice, Hoon is 100% a functional language, but you can lay your code out so that it reads like a sequence of actions.
223 |Maybe it’s because I spent some of my crucial formative years doing turtle graphics, but this maps well to how my brain works.
224 |Well-styled Hoon should flow this way. If you have an if statement, ?:, and you find that most of the “meat” happens when the test is true, use the inverted test, ?., so the true clause happens last.
I think of style as “soft syntax”. It’s syntax that doesn’t have to be formalized to the point that something as stupid as a compiler can handle it. It’s syntax you can ignore if you have a good reason. And you can change style without having to push out a new version.
226 |Every operation is evaluated against one operand, the subject. It will return one result, the product.
There’s no “environment” or “scope”. There’s no symbol table. Just the subject.
229 |The subject, by default, contains the standard libraries, info about the ship we’re running this on. But we can always add things to the subject, and explicitly choose a limited subset of the subject to work with.
230 |I described the syntax for Hoon as “terrifying”, and I’m sticking with it. I think most Hoon developers would agree.
232 |Syntax here means, what do you have to type out to do the thing you’re trying to do? How wordy and complicated is it?
233 |This is in contrast with semantics which means, what do the things you’re typing out mean? Conceptually, how easily can this fit into your head?
234 |Another way to look at it is that syntax is the front-end, the thing the developer types in. Semantics is the back-end, what the machine is capable of doing with it?
235 |In any language, there’s a tension between syntax and semantics.
236 |Some poorly designed languages can have complicated syntax and complicated semantics, but at some point, simplifying the one will come at the expense of making the other more complicated.
237 |A good example of this is static types vs. dynamic types.
238 |Dynamic types simplify the syntax for the simple reason that you don’t have to specify a type. Something like this is perfectly valid in JavaScript:
239 |thing = 23
240 | thing = "changed my mind"
241 | thing = 12.3
242 | This comes at the expense of not exactly knowing what thing is. If I start using it in some other part of the code, what value is it going to have? Can I append a string to it? What’s that going to look like?
I’m picking on JavaScript because it’s a notorious semantic dumpster fire. This is because the interpreter will accept pretty much anything as valid syntax, even things that don’t make any sense semantically.
244 |Nock has very simple syntax and very simple semantics. There’s nothing that’s a big secret going on here. The problem is that, by itself, it’s of extremely limited utility. You don’t have to sweat how type inference works in Nock because Nock has no types.
245 |Which brings up the third point. Expressiveness. What concepts do the language make possible? How easy is it to put these ideas into code?
246 |So I’ve made a handy diagram to condense this idea. This is the most important diagram in this article. I present:
247 |
Please note: this idea is not particularly original. What is original is making it into a triangle shape and naming it after myself.
252 |When you give something a triangle shape, you’re saying, “Pick any two”.
253 |Hoon has very complicated syntax but the semantics are actually fairly straightforward. There’s no magic happening inside the compiler. If you understand the code, you pretty much understand what the compiler is doing.
254 |However, even though the semantics are simple, they’re chosen in such a way that you can express some really powerful ideas with them.
255 |I would dearly love to run you through all the ins and outs of Hoon, but they gave me 50 minutes for a talk, and the above content used up a ton of them.
257 |So for the remainder, I’d like to drill a bit into types and how they work in Hoon.
258 |Hoon is a strongly-typed language, but its type system works completely differently, and way more simply, than most other strongly-typed languages.
259 |Hopefully you can get a flavor of how close we can get to the fully-armed and operational bidirectional type inference system which Hindley-Milner makes possible, only with 1/1000th the complexity.
260 |So, let’s talk about what a type actually is. A type is a set of values which conforms to some kind of semantic criteria. It could have infinitely many possible values or it could have zero possible values. Or one possible value.
262 |(Side note: what’s the difference between a type with one value and a constant? A: There isn’t any.)
263 |Conveniently enough, this definition is really close to the definition of a function. A function takes as input some domain, does stuff, and its output is some other domain, the range. All functional languages work like this, and Hoon is a functional language.
264 |So for a minimalist type system, all you really need is the humble function. You could take as the domain any value and validate that it conforms to your semantic criteria. If it does, pass it along as-is. If it doesn’t, pass along some default value that’s still part of your type domain.
265 |Not at all coincidentally, this is how a mold works in Hoon, as a type constructor function.
As a thin layer over Nock, Hoon continues to use the noun as its one way of representing a value. This certainly makes the type-constructor-as-function thing quite a bit easier, since the possible values we might want types for are numbers and binary trees of numbers.
Hoon adds a few additional bits of decoration and functionality to make everyone’s life easier.
269 |In Nock, we represented all data contents as unsigned integers, which we said was pretty useless but we’d deal with it in the future. Well, the future is now.
270 |So, an arbitrary-length unsigned integer is basically a collection of bytes. So really, anything that can be represented as bytes can be an atom:
271 |If we know what our atom represents, we can assign a soft type, or an aura to it. All but the last three on this list are valid atoms, by the way, including the bitcoin wallet.
Auras specialize to the right:
283 |@ => any atom
284 | @t => UTF-8 text (a `cord`)
285 | @ta => ASCII text
286 | @tas => ASCII text symbol (lowercase letters and digits only)
287 | Auras are truly soft types. Underneath it all, they’re still just integers, and you can convert any aura to any other aura, if you first convert it to @.
@c UTF-32 ~-foobar
289 | @da 128-bit absolute date ~2016.4.23..20.09.26..f27b..dead..beef..babe
290 | ~2016.4.23
291 | @dr 128-bit relative date ~s17 (17 seconds)
292 | ~m20 (20 minutes)
293 | ~d42 (42 days)
294 | @f loobean & (0, yes)
295 | | (1, no)
296 | @p ~zod (0)
297 | @rs 32-bit IEEE float .3.14 (pi)
298 | .-3.14 (negative pi)
299 | @sd signed decimal --2 (2)
300 | -5 (-5)
301 | @ub unsigned binary 0b10 (2)
302 | @uc bitcoin address 0c1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa
303 | @ud unsigned decimal 42 (42)
304 | 1.420 (1420)
305 | @ux unsigned hexadecimal 0xcafe.babe
306 | A non-exhaustive list of auras and their associated literals.
307 |The syntax for our data literals is a bit complicated for two reasons: 1. They’re designed to be URL-safe 2. They all have different parse rules
308 |This is an example of the tradeoff between syntax and semantics. Once you know the rules for what a float looks like — =/ pi .3.14159 — our understanding of what kind of this number this is is exactly the same as the compiler’s.
One aura that is unique to Urbit is @p, which is the phonemic base. This is a pronounceable, base-256 representation:
~leb => 145
311 | ~samtul => 1066
312 | So, let’s take these auras for a spin.
313 |> (add 70 4)
314 | 74
315 | > ? (add 70 4)
316 | @
317 | 74
318 | If we use wut, we can get the return type for this function, which is @ or “any atom”.
> ^- @t (add 70 4)
320 | 'J'
321 | > ^- @ 'J'
322 | 74
323 | We can use ^- or kethep to cast the return value to @t or “text”. We can also go the other direction and cast ‘J’ back into a generic atom.
> ^- @t 74
325 | /~ribben-donnyl/home/~2018.5.27..17.31.49..d55f/sys/vane/ford:<[1.290 24].[1.290 52]>
326 | nest-fail
327 | \/ford: build failed ~[/g/~ribben-donnyl/use/dojo/~ribben-donnyl/inn/hand
328 | /g/~ribben-donnyl/use/hood/~ribben-donnyl/out/dojo/drum/phat/~mall\/
329 | et-rilmul/dojo /d //term/1]
330 | \/
331 | > ? 74
332 | @ud
333 | 74
334 | If we try to cast 74 directly to text, we get a nest-fail. When we call wut to see what our type is, it’s @ud, an unsigned decimal.
So you can make auras more specific or less specific, but you can’t alter their type directly. You can’t convert from the “unsigned” type @u to the “text” type @t.
> `@t``@`74
337 | 'J'
338 | The shorthand for casting is to use tecs, i.e. backticks. We can do this conversion if we cast the @ud to @ or “any atom” first, and then cast to @t.
> `@p``@`.127.0.0.1
340 | ~bidpur-hidpex
341 | Here’s our phonemic base in action. All the addresses for personal IDs in Urbit are the size of an IP address.
342 |Here’s my personal ship cast to an IP address:
343 |> `@if`~ribben-donnyl
344 | .33.165.1.0
345 | @if is “IP v4 address”. I think the pronounceable version is more memorable, but your opinion might vary.
Finally, here’s a look at signed numbers.
347 |> `@`--51
348 | 102
349 | > `@`-51
350 | 101
351 | Atoms are arbitrary-length, you can’t do the usual two’s complement, since there’s no way of knowing how many bytes you’ll need. Instead, the sign bit is the lowest bit. Positive n is n*2, negative n is n*2-1.
In Nock, we have one operation to extract data from our binary tree, which is to give it an address.
354 |1 is the address for the whole tree, 2 is the address for the left subtree, 3 is for the right subtree, etc.
355 |
In Hoon, we can add label these nodes, which are called “surfaces” or just “faces”. To get some node or subtree out of a noun, you give it a face and Hoon performs a depth-first search and returns the first element it finds.
359 |Using these faces, you can make your noun work like an associative array, or dictionary, or hash table, or whatever your language calls them.
360 |
There is no symbol table in Hoon. Just a depth-first search.
364 |Hoon has the equivalent of the Either type, only better! You are not limited to a Left and Right. You can declare a type that is a union of an arbitrary number of other types.
One of these union types is unit, which works like a Maybe, or an Option, or a Possibly.
This has two acceptable values, either the null value, $~, which means Nothing, or None. Then there’s [~ a], which means either Just a or Some a.
And in case anyone is wondering, the Hoon standard library supplies the axiomatic Monadic functions, bind and just, which Haskell calls return.
In Urbit, typechecking is called nest-ing. So, a type is a set of values. And a type check verifies that this type’s set of values “nests” inside some other type’s set of values? Is it a subset?
Say we want a type to represent a dog. I mentioned we can use faces and make an associative array. So let’s do that.
372 |> =dog {name/@t color/@ta date-of-birth/@da}
373 | Urbit comes with a built-in Hoon REPL. It’s got some non-standard features, like this =dog, which creates a variable called dog. And dog is a type, with three labeled attributes, a name, which is any UTF-8 text, a color, which is limited to ASCII, and date of birth, which is an absolute date.
> =bruno [name='Bruno🐶' color='faun' date-of-birth=~2016.12.10]
375 | > ? bruno
376 | {name/@t color/@t date-of-birth/@da}
377 | [name='Bruno🐶' color='faun' date-of-birth=~2016.12.10]
378 | We make another variable called bruno.
Using the ? or wut here, we can verify Bruno’s type. We didn’t declare Bruno with any reference to our dog type, but he’s got the same tree “shape”, faces, and compatible auras.
> ?? bruno
381 |
382 | [ %cell
383 | [%face [~ %name] [%atom %t ~]]
384 | [ %cell
385 | [%face [~ %color] [%atom %ta ~]]
386 | [%face [~ %date-of-birth] [%atom %da ~]]
387 | ]
388 | ]
389 | [name='Bruno🐶' color=~.faun date-of-birth=~2016.12.10]
390 | We can all do a wutwut, to see Bruno’s uncensored type, its type as a noun.
Internally, this type is composed of several subtypes, for cells, faces, atoms, etc.
392 |> `dog`bruno
393 | [name='Bruno🐶' color=~.faun age=~2016.12.10]
394 | We attempt to cast bruno as a dog. Bruno is, in fact, a valid dog.
395 |Let’s make another dog, called rex, with a completely invalid date of birth.
> =rex [name='rex' color='hairless' dob=42]
397 | > `dog`rex
398 |
399 | /~ribben-donnyl/home/~2018.5.27..17.31.49..d55f/sys/vane/ford
400 | <[1.290 24].[1.290 52]>
401 | nest-fail
402 | \/ford: build failed ~[/g/~ribben-donnyl/use/dojo/~ribben-donnyl/inn/hand /g/\/
403 | ~ribben-donnyl/use/hood/~ribben-donnyl/out/dojo/drum/phat/~ribben-donnyl/do
404 | jo /d //term/1]
405 | \/
406 | When we try to cast rex as a dog, we get a nest-fail. rex is a bad dog.
I mentioned that molds are functions. You can actually call them and provide them with a sample. If the sample is of the correct type, it will return the sample. If it isn’t, it will return a default value.
408 |You don’t usually do this directly. Usually the compiler takes care of types for you. But you can if you want to validate untrusted network traffic, for instance.
409 |So let’s say we have a service that accepts a dog as input.
410 |> (dog bruno)
411 | [name='Bruno🐶' color=~.faun date-of-birth=~2016.12.10]
412 | We call dog with bruno, and we get back bruno.
> (dog 17)
414 | [name='' color=~. date-of-birth=~292277024401-.1.1]
415 | We call dog with a silly value, we get back a dog with all its values filled with defaults.
So the mold doesn’t necessarily give you back the value that was sent over the wire. But it doesn’t give you something malformed or malicious either.
417 |> (dog rex)
418 | [ name='rex'
419 | color=~.hairless
420 | age=~292277024401-.1.1..00.00.00..0000.0000.0000.002a
421 | ]
422 | If we call it with rex, it returns a valid dog and it converts rex’s weird date of birth to a valid absolute date.
Since nouns in Hoon are atoms, trees, and node labels, for the developer, figuring out the “type” of a value is often a matter of pattern matching. In Hoon this uses the ?= or wuttis rune.
> ?=({color/@ *} bruno)
425 | %.y
426 | In this instance, we’re just asking if bruno’s type has a node labeled color, which contains an atom. The * means “any noun” so we want bruno to have any other value to pattern match.
And %.y means this matches.
> ?=({color/* @ @ @} bruno)
429 | %.n
430 | If we asked if bruno has color and three more nodes, this fails.
Let’s make a cat type.
> =cat {color/@ta date-of-birth/@da}
433 | Unlike the dog, it’s got two values, a color and a date of birth. (It has no name, because it’s not like if you call its name, a cat is going to come to you.)
> =pet $?(dog cat)
435 | Now using $?, or buckwut, we make a union type, a pet, which is either a cat or a dog.
> `pet`bruno
437 | [name='Bruno🐶' color=~.faun date-of-birth=~2016.12.10]
438 | Then we try to cast bruno as a pet and it works.
I’ve thrown out the word “function” many times, as you’d expect for a conference on functional programming. However, the details are a bit more complicated than you might assume from the term “function”.
441 |Functions in Hoon appears inside a core, which is a cell with two parts: the code and the data. The code takes the form of a list of compiled attributes, called arms.
A gate is a special case, being a core with one nameless arm, i.e. a lambda.
This is a recurring pattern in Hoon. If you can make one of something, you can also make a whole list of something, and the one-of-something is a special case.
444 |Cores serve the same function as objects in most other languages, since they contain both methods and data.
445 |(Side note: “Nameless Arm” would make a pretty good album name)
446 |Types are only really important within the context of a function. What sorts of values can this function handle as input, and what sort of values is it going to return?
448 |In Hoon, you do not declare a type. They’re only inferred by the compiler. You pass code to the compiler and it returns a pair of a type and Nock code.
449 |Type inference in Hoon only happens forwards, not backwards. Which means the compiler will determine the return value of a function, but won’t figure out the types of the function arguments.
450 |This means that, since you can’t rely on the compiler to figure out all your types, you have to annotate them. Although this arguably makes your code more readable.
451 |Sometimes, you might choose to annotate where it isn’t technically needed. Looking at our Fibonacci program from above:
452 ||= n/@ud :: 1
453 | ^- (list @ud) :: 2
454 | =/ a 0 :: 3
455 | =/ b 1 :: 4
456 | |- :: 5
457 | :- :: 6
458 | a :: 7
459 | ?: =(0 n) :: 8
460 | ~ :: 9
461 | $(n (dec n), a b, b (add a b)) :: 10
462 | On line 2, we cast the results of everything that follows to a list of unsigned decimals. Since this is the first line in this function, this will end up the inferred type for this whole program.
463 |This does a few things:
464 |nest-fail, right there at line 2, rather than somewhere else in the app.This applies to pretty much any language. Just because the compiler will infer what you’re doing, unless you’re doing something explicitly generic, it won’t kill you to type out what you’re expecting and can save you some trouble.
470 |As it happens, if you leave out the cast, the inferred type is almost a list of unsigned decimals… but not exactly. The compiler tends to err toward being too generic and it can drop information you might be expecting.
471 |You can end up with really subtle bugs if you just let the compiler do all the work for you.
472 |So here’s a function to make a tuple, an arbitrary-length list:
473 |> :+(42 420 %gocards)
474 | [42 420 %gocards]
475 | We can call the parser from the command line:
476 |> =parsed (ream ':+(42 420 %gocards)')
477 | > parsed
478 | [%clls p=[%sand p=%ud q=42] q=[%sand p=%ud q=420] r=[%rock p=%tas q=32.480.064.744.681.319]]
479 | Then we can pass this parsed expression to the compiler:
480 |> (~(mint ut %noun) %noun parsed)
481 | [#t/{@ud @ud $gocards} q=[%1 p=[42 420 32.480.064.744.681.319]]]
482 | The q is our compiled Nock code. And the #t is our type, which says, as we were expecting, it’s a tuple with two unsigned integers and a constant, gocards.
(Go Cards!)
484 |We don’t want to have to write a different function for every type if we don’t have to, and we don’t have to in Hoon through the magic of polymorphism.
486 |What we’ve been describing is, in Hoon terms, “dry polymorphism”, or “variance”. This uses Liskov substitution, which is the same type checking we were using above when we were talking about auras and dogs.
487 |However, we’re leaving a whole lot of tasty functional goodness if we don’t talk about the other sort of polymorphism, “wet polymorphism” or “genericity”.
488 |When we use wet gates, our code is compiled, but we defer doing our full type inference until we actually use the function. Then we infer the type based on the inputs we are given. So the function acts something like a macro.
489 |So, here’s a very simple function that will work for pedagogic purposes.
490 ||= p/(list *) :: 1
491 | |- :: 2
492 | ?~ p :: 3
493 | ~ :: 4
494 | :- i=i.p :: 5
495 | t=$(p t.p) :: 6
496 | This program accepts a list and recursively rebuilds the same list.
497 |> `(list *)`(limo ~[1 2 3])
498 | ~[1 2 3]
499 | limo will take a null-terminated tuple and make a list out of it.
> +rebuild (limo ~[1 2 3])
501 | ~[1 2 3]
502 | So far so good. Now, what if we use a tape, which is a string represented as a list of characters?
> +rebuild "tape"
504 | ~[116 97 112 101]
505 | This is not what we wanted!
506 |The problem is, when this compiled as a dry gate, it set the type of the list to *, which is “any noun”. Then when we call it, it casts the characters in our string to their raw ints and that’s what we build a list out of.
|* p/(list) :: 1
508 | So we change our |=, which makes a dry gate, to a |*, which makes a wet gate.
> +rebuild "tape"
510 | "tape"
511 | And when we run it, we get our string back.
512 |The standard library uses wet gates for things like folds, maps, and the sorts of things you might use a typeclass for in Haskell.
513 |At this point, I would dearly love to launch into a discussion on how cores work, some of the more interesting details on type inference, and the menu of options for polymorphism open to you. It’s got a much more extensive menu of options so much more than wet vs. dry.
515 |However, that’s all we have time for. However, I will be happy to discuss cores, type inference, and polymorphism with everyone at LambdaConf 2019.
516 |So, here’s what I’m hoping everyone takes away from this, even if they never look at anything Urbit-related ever again:
518 |The amazing possibility of computing based on nothing but unsigned integers, binary trees, and increment.
ASCII Pronunciation
Give it a try! You’ll find coding to be a different experience when you can subvocalize what you’re typing out.
523 |I think Kelvin versioning would work well with a REST endpoint that’s under development. You might have some other developers who are also developing a client to consume your data, and giving your return value a Kelvin version lets them know how close you are to being “done” and if something has changed.
527 |
I’m serious about that one. Spread the word on that. Work it into conversation with your friends and in-laws. Don’t forget to call it “LeBlanc’s Triangle”.
534 |If that takes off, you can be a total hipster about it and say, “Yeah, I was there at the conference when the eponymous LeBlanc introduced it.”
535 |Really, everyone’s a winner on that.
536 |And finally, to quote the Urbit whitepaper:
537 |538 |540 |“Urbit is cool and you should check it out.”
539 |
This is a good time for that. After this latest version of Hoon, development is being transitioned out of “auteur” mode and handed over to the developer community.
541 |Hoon makes no use of category theory. Should it? What are we missing without it? Get involved and make the system better.
542 |Install Urbit and have a look.
544 |Then check out the stream. You can chat with people, possibly myself, and pepper everyone with questions.
545 |If you see ~ribben-donnyl on there, that’s me. Say hello!
Also, you might have seen me walking around with a variety of cool Urbit shirts. They’re available for purchase.
547 |