4. First node
Goal
Put a node on the stage that counts, then start a second one and crash the first.
New in this chapter
n.Countform member spec/01 §1.4define [n: Node] startform start spec/05 §5.3Node hasform has-node spec/05 §5.2set n.Count to …form set-field spec/05 §5.2[n: Node]type Node spec/04 §4.2[p: package]type package spec/04 §4.2define [n: Node] startcontext node spec/05 §5.4
Steps
Until now one program did everything. A real system is many small programs running at once, each with its own memory. Synod calls such a program a node, and the code that describes how a node behaves lives in a package: a folder of .synod files.
Packages live in a module, a folder with a file synod.mod at its root. From here on you keep one module that grows from chapter to chapter. Create it with synod new:
synod new tutorial --module example.com/tutorial
cd tutorial
The command writes synod.mod, a .gitignore and a first package, tutorial/, with two files. Step 1 is exactly that output:
Step 1
# package main
use example.com/tutorial/tutorial
use std/sim
define main
set first to (sim.Spawn tutorial)
after 3s
sim.Crash first
sim.StopAt 6s
# package tutorial
use std/time
Node has
set Count to 0
# Count up once a second
define [n: Node] start
time.Every 1s
set n.Count to (n.Count plus 1)
module example.com/tutorial
synod 0.2
Two files, two jobs. tutorial/tutorial.synod describes the node. Node has lists what each node remembers: here a number Count, which starts at 0. define [n: Node] start is what a node does when it is created. The hole [n: Node] gives the node a name, n, inside the block, and n.Count reads or writes a field of that node: a . joins a node to what belongs to it. time.Every 1s runs its block now and then once a second, and set n.Count to (n.Count plus 1) is the same set you know from chapter 2, now on a field instead of a local.
tutorial/demo.synod is the program. It starts with # package main, as in chapters 1 to 3, and uses the package it lives next to with use example.com/tutorial/tutorial: the module path, then the package folder. sim.Spawn tutorial creates one node that runs that package, and gives it back, so set first to (…) keeps it. Three seconds later sim.Crash first stops the node. Run it:
synod run tutorial
The package is named tutorial, like the module, which will get confusing. Rename the folder to counter, and change the header and the use lines to match. Step 2 is the same program with the new name:
Step 2
# package counter
use std/time
Node has
set Count to 0
# Count up once a second
define [n: Node] start
time.Every 1s
set n.Count to (n.Count plus 1)
# package main
use example.com/tutorial/counter
use std/sim
define main
set first to (sim.Spawn counter)
after 3s
sim.Crash first
sim.StopAt 6s
The header must always say the folder's name. Rename only the folder and synod check stops you, see the note at the end of the chapter.
One node is lonely. Step 3 spawns two nodes at the start of define main and crashes them at different moments. Each node has its own Count, so the two counters are independent, and a crashed node stays where it is:
Step 3
# package main
use example.com/tutorial/counter
use std/sim
define main
set first to (sim.Spawn counter)
set second to (sim.Spawn counter)
after 3s
sim.Crash first
after 5s
sim.Crash second
sim.StopAt 8s
Last, a word about capitals. In Synod a capital letter has exactly one meaning: exported, visible from other packages. Count starts with one, so the demo, the animation and other packages can read it. Step 4 adds a lower-case field, ticks, that only this package can see:
Step 4
# package counter
use std/time
Node has
set Count to 0
set ticks to 0
# Count up once a second
define [n: Node] start
time.Every 1s
set n.Count to (n.Count plus 1)
set n.ticks to (n.ticks plus 1)
Run it again and compare the final lines: ticks is not among the fields shown, because nobody outside counter may look at it. Fields, phrases and records that other packages need are capitalized; everything else is not.
What to watch
Each node is a card on the stage with its Count. The counter of the first node goes up once a second until the crash, when its card shows that the node is down; the second node keeps going. Look at the trace: the node starts at 0s with Count 1, because time.Every runs its block right away and then every second.
Why it looks like this
The package header is the folder name, and the compiler checks it, so that a file can never say one thing while its place says another. In this module the folder counter still holds a file that says # package tutorial:
counter/tutorial.synod:1:1: error[bad-header]: package "tutorial" must be the folder name "counter" or main
|
1 | # package tutorial
| ^^^^^^^^^^^^^^^^^^
|
= note: a main file writes "main"
help: write the folder name "counter"
|
1 | # package counter
| ~~~~~~~
|
bad-headersynod check answers error[bad-header]: the package named in the file must be the folder name (or main). Fix the header, not the folder.
Exercises
- Predict
Countof both nodes at the end of step 3, then run it.
Expected
The first node ticks at 0s, 1s and 2s and is crashed at 3s before its fourth tick, so it ends at 3. The second ticks at 0s to 4s and ends at 5.
- In step 3 swap the two crashed names, so that
secondis crashed at 3s andfirstat 5s. What changes?
Expected
The second node stops at 3s with Count 3, and the first keeps counting until the crash at 5s and ends at 5: the two final counts swap.
- Make the counter go up by two each second.
Expected
Change (n.Count plus 1) to (n.Count plus 2). Counts go 2, 4, 6…