· 6 min read
Following the wire
Bend becomes understandable once its HVM output is read as an interaction-calculus wiring diagram whose independent active pairs can reduce in parallel.

I ran bend gen-hvm on an identity function and got @main = (a a). For several minutes I read it as a useless expression applying a to itself. The compiler had removed every familiar clue, including the distinction between a binder and its use.
The repeated a is the clue. Its two occurrences are the ends of one wire. HVM is a wiring diagram written sideways: Bend gives the graph functions and datatypes, HVM records its connections, and the evaluator computes when two principal ports meet.
Begin with the pinned runtime#
The current Bend manifest identifies the pair precisely: Bend 0.2.38 pins hvm = "=2.0.22", and the HVM2 manifest declares that version. The reproducible installs are cargo install hvm --version 2.0.22 --locked and cargo install bend-lang --version 0.2.38 --locked.
Bend's smallest golden test gives us a clean entrance. The source on the left compiles to the net on the right:
main = λa a@main = (a a)Parentheses create a constructor node with two auxiliary ports. Every node also has an implicit principal port. Because a wire joins exactly two ports, an HVM variable name normally appears exactly twice; both as above describe one connection rather than two stored values.
Computation is a collision#
The HVM2 paper calls its textual language an Interaction Calculus representation of an interaction-combinator net. A definition contains one root tree plus optional equations after &. The~ operator connects principal ports, creating an active pair that can rewrite.
I wrote the identity application directly in HVM so the pair stays visible:
@id = (a a)@main = out & @id ~ (42 out)The reference @id first expands to its net. Two constructor nodes then face each other, so the annihilation rule connects their corresponding auxiliary ports:
(a a) ~ (42 out)-----------------a ~ 42a ~ outThe two equations containing a link together, leaving the root connected to 42; HVM 2.0.22 reports Result: 42. Other meetings use different local rules. A nullary eraser propagates through a binary node, while constructor and duplicator nodes commute by building four new connections. The two agent types determine the rule.
Copying has somewhere to happen#
Duplication is easy to miss in ordinary source because writing a variable twice looks free. This deliberate Bend test names the operation, and its compiled net contains the curly-braced duplicator node:
main = λa let {a1 a2} = a; (a1 a2)@main = ({(a b) a} b)When that duplicator meets another binary node, the commute rule pushes copying through the structure locally. Separate active pairs can take separate workers because neither rewrite needs a global program counter. References, native numbers, operators, and numeric switches extend the small combinator system so arithmetic does not arrive encoded one lambda at a time.
Bend exposes the branches#
The official paired example builds a balanced tree and counts its leaves by adding one at each base case. Bend contains no thread creation. The two recursive calls in sum expose independent work through the shape of the expression:
gen = λd switch d { 0: λx x _: λx ((gen d-1 (+ (* x 2) 1)), (gen d-1 (* x 2)))}sum = λd λt switch d { 0: 1 _: let (t.a,t.b) = t (+ (sum d-1 t.a) (sum d-1 t.b))}main = (sum 20 (gen 20 0))Its generated HVM is longer, but the relevant piece becomes recognizable after circling each wire name twice. The switch is ?, the addition is a native $ operator, and the two recursive calls are separate redexes:
@sum = (?(((* 1) @sum__C0) a) a)@sum__C0 = ({a c} ((b d) f)) &! @sum ~ (a (b $([+] $(e f)))) &! @sum ~ (c (d e))The &! marker flags these branching calls for the CUDA work-sharing policy. CPU workers steal redexes from one another without that hint, while suitable GPU threads receive similarly shaped recursive work. A final addition still waits for both branches. Bend exposes the independence and HVM schedules the active pairs it creates.
I am still unable to reconstruct a large Bend function fluently from generated HVM, and the HVM2 paper itself remains a work in progress. The useful threshold arrived earlier. I left the two sum_tree files side by side, underlined every ~, and paired the letters with a pencil; the recursive branches appeared, while the rest of @sum__C0 remained punctuation for another morning.