Skip to content
This repository has been archived by the owner on Jul 13, 2020. It is now read-only.

Latest commit

 

History

History
97 lines (63 loc) · 7.79 KB

2.md

File metadata and controls

97 lines (63 loc) · 7.79 KB

← Part one: Introduction and setupPart three: Connecting to talk →

Urbit By Doing

Making a talkbot

Part two: A very basic Hoon program

We must begin where we are and move forward immediately by starting small and capitalizing on what's at hand.
-- Mike Schmoker

Let's start off by getting our bearings. We'll be writing a very simple Hoon program that takes some input and prints it. It's not fancy at all, but it allows us to look at some Hoon code without distractions to see what's going on, how the logic flows.
Again, keep in mind that we're going to try and look at things using familiar terms and concepts. Some subtleties will be lost in translation, but they'll make themselves apparent as we start doing more interesting things later on.

For every part of this series, I'll be including a Hoon file with the end result. You can find the little bit of code from this part here.

Nine lines of Hoon

Start off by making a new file in mytalkbot/home/app named mytalkbot.hoon. Open it up in your favorite editor, and type in the following lines, paying attention to single and double spaces. (Yes, I heartily recommend you re-type it rather than copy-pasting. Help yourself learn!)

::  Bot for talk.
!:
|_  {bowl state/$~}
++  poke-noun
  |=  a/*
  ^-  {(list move) _+>.$}
  ~&  [%poked-with a]
  [~ +>.$]
--

Let's read through that code and explain it, line by line.

The first line is a comment. :: is equivalent to // in other languages, nothing fancy there. (Hoon doesn't have block comments, in case you were wondering.)

!: is a rune that enables debug functionality like stack traces for all the code below it. Very useful to have when you're developing!

|_ begins the definition of a core, and the -- at the end of the code closes it. The exact "how" and "why" of cores isn't relevant yet. For now you just need to know that |_ means the code within it has access to the state we set our core up with.

{bowl state/$~} is that state, or rather, the definition of it. It is a so-called mold, which you can view as a type. The cool thing is, we can make up molds on the spot! The curly braces group its contents together in a cell mold. This means the value-equivalent of our mold would look like [bowlvalue statevalue]. The bowl is a collection of global state data, which lets you access things like the current time and your own identity. state/$~ defines a face (variable name, basically) of type $~, which is like a null type, meaning state can only be null (~). It is essentially a dummy variable, we'll change it later to be more useful, but for now it's just a placeholder that ensures the core works properly.

++ poke-noun defines the one arm in our core. For now, think of arms as names we attach to gates (which we'll describe in a bit). The poke-noun gate-name is special, in that it automatically gets called whenever our application gets poked ("called") with input of type noun — that is, any input. If we only wanted to take atoms (integers), we would've specified poke-atom instead.

|= forms a gate, which is like a function. It takes a sample (argument) and runs the code we've written under it. a/* is our definition of that sample, which says we expect it to be of type * (a noun, either an atom (integer) or cell (pair of nouns)) and to store that sample's value under the face (variable name) a.

^- casts whatever we will be producing (returning) in the code after it to the mold (type) we specify. Remember how ++ poke-noun got automatically called? The OS handles that for us, but in return it expects a specific kind of output from us. {(list move) _+>.$} is the mold for that output, wanting a cell consisting of a list of moves (actions for the system to make, we'll get to those later) and our (possibly updated) application state. Let's not worry about the specifics behind _+>.$ for now.

~& is one of the ~ runes, used for hinting and debugging. ~& is a printf for debugging, it pretty-prints whatever we pass in. I personally find it a good idea to tag those prints with %, to easily see where a specific print came from. [%poked-with a] is a cell (pair of nouns) consisting of a tag, and our data. (We'll elaborate more on these tagged structures in the next part!)

Lastly, [~ +>.$] is what we produce (return). It is a cell that complies with the cast we made above (^-). ~ is null (also called nil), which is exactly what an empty list looks like. +>.$ is our current application state. Again, let's not worry about its specifics for now.

On runes and code flow

If you look at the code above, you'll see it flow more or less straight down. And yet, in our example, each line of code is "part" of the one above it. For example, where we have our |= a/* line, everything below it is part of the expressions we pass to that rune. This may be more obvious in wide-form, which doesn't do newlines and uses parentheses to keep rune expressions grouped.

|=(a/* ^-(etc etc))

You'll find that most runes "do a thing, then compute our last expression". That last expression is technically part of our rune expression, but in tall-form (used above) we don't indent that last expression after putting it on a new line. If we did, the following would happen:

|=  a/*
  ^-  {(list move) _+>.$}
    ~&  etc...

To prevent that, we generally only want to indent when our code branches (for example, when doing an if-else). This way, the code flows downwards like we're used to, hiding all the "nesting" from us.

As a rune-related side-note: Maybe you've noticed it already, but runes are grouped by their first character. For example, I mentioned how all ~ runes are used for hinting and debugging. Now whenever you see a ~ runes in the wild, even one you haven't ever heard of, you'll be able to infer the general function it is fulfilling. Try and identify the different groups as we progress, or look them all up in the docs!

Do it in dojo

After you've saved your code, head over to your ship's dojo. Execute the following two lines:

|start %mytalkbot
:mytalkbot 'hello!'

Let's explain what we did real quick.
|start %appname starts (activates) the app located in app/appname.hoon. So if you named your talkbot's code file differently, this command should reflect that change.
:appname input pokes the app with the input, which can be any kind of data. If you ever need to interact with apps you have running, this is most likely how you'll be doing it.

Looking at the output we got from poking our program, you may have noticed something peculiar. Our ~& didn't print [%poked-with 'hello!'] as you might have expected, but came up with [%poked-with 36.762.444.129.640] instead. That is because strings within ' single-quotes (called cords) are seen as @ atoms (integers). (The mold for a cord is @t.) If ~& had know it was dealing with a cord, then it would've neatly printed hello!, but since all information it had about a was that it's a * (noun), it just printed what it saw: some kind of atom.
Try poking the program with "hello!" instead. Notice what it's doing now? Strings within " double-quotes (called tapes) are seen as a list of characters ((list @tD)). We'll learn more about their differences and uses later.

Next time

Was that a lot to take in, or were you familiar with some of that already? Either way, take a short break. Go for a walk, drink some water.
In the next part, we'll try to get our application to respond to "commands" for subscribing and unsubscribing to different talk channels. Prepare yourself, it's going to be almost thrice as long as this part!

Relevant reading

Hoon: Syntax
Urbytes

← Part one: Introduction and setupPart three: Connecting to talk →