Pure Nodes and Verse's Effect Specifiers
The main lesson said "nodes become function calls", but left one thing out: Blueprint nodes come in two shapes, with white execution pins and without. That distinction did not vanish in Verse — it moved somewhere much stricter: the function signature.
1. Look at the Pure Node Properly First
Some Blueprint nodes cannot take a white wire: Add, Get, Make Vector, Greater Than, Get Player Count. They have data pins only and no execution pins. These are Pure nodes, drawn as small green rounded boxes. They have three traits:
- They don't queue up: not being on the white wire, "when do they execute" simply isn't a question.
- One use, one computation: three wires out of the output pin means three evaluations (see section 5 of the main lesson).
- They shouldn't change state: this is a convention — Pure nodes are assumed to compute without modifying.
The load-bearing word in the third trait is convention. Tick the Pure option on a Blueprint function and the engine does not verify whether you quietly Set a variable inside the body. You ticked Pure, so it looks like a Pure node — that's the whole mechanism. The C++-side BlueprintPure marker works the same way: it's a promise written by the node's author, not a contract that gets checked. So in Blueprints, the accuracy of "is this node pure" depends on how disciplined its author was.
2. Verse Writes It Into the Function Signature
Verse's equivalent mechanism is the effect specifier, written in angle brackets after the function name and before the return type. You have been looking at one all along — the <suspends> at the end of the OnBegin<override>()<suspends>:void that shows up in every lesson.
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
score_device := class(creative_device):
var Score:int = 100
# Pure computation: takes arguments, returns a result, touches no mutable state
Doubled(X:int)<computes>:int = X * 2
# Might fail: passes if you can afford it, "fails" if you can't
Afford(Price:int)<decides><transacts>:void =
Score >= Price
# No specifier = <transacts>: reads and writes, like a normal node on the white wire
AddScore(N:int):void =
set Score += N
OnBegin<override>()<suspends>:void =
Print("{Doubled(21)}")
if (Afford(30)):
AddScore(-30)
Print("Bought it, {Score} left")
Three functions, three identities. Doubled is a Pure node through and through; AddScore is an ordinary node on the white wire (it sets a variable); Afford is a third thing Blueprints don't have — a function that "might not go through", which must be called somewhere failure is allowed, such as inside an if.
3. The Five You'll Hit First
There are more than five specifiers, but these five cover 95% of what you meet while migrating. What follows is "roughly what each governs"; defer to the official docs for exact semantics:
| Specifier | Roughly what it governs | Blueprint analogy |
|---|---|---|
<computes> |
Pure computation: the same input always yields the same output; neither reads nor writes mutable state | The purest kind of Pure node (Add, Max and friends) |
<converges> |
The "smallest" effect set: guaranteed to yield a value in finite time, never diverging | No counterpart — Blueprints don't care whether your function can hang forever |
<transacts> |
The default tier: may read, write and allocate; those actions sit in a transaction and can be rolled back together on failure | An ordinary (Impure) node on the white wire |
<decides> |
This function can fail; calling it is an expression that "might not go through", and it must be written somewhere failure is allowed | Adding a bool output pin and wiring a Branch to check it by hand |
<suspends> |
This function can pause, hand control to other concurrent work, and take several simulation updates to finish | Latent nodes (Delay, Timeline — the ones with a clock icon in the corner) |
One combination rule is worth memorizing: specifiers split into exclusive and additive. <converges> / <computes> / <transacts> are exclusive — a function picks at most one. <decides> / <suspends> / <reads> / <writes> / <allocates> are additive and can be written alongside an exclusive one, as in the <decides><transacts> above. There is one hard restriction: <decides> and <suspends> cannot be used together — "can be rolled back" and "can wait across frames" don't mix.
And if you write nothing at all? Per the official docs, a function with no specifier defaults to <transacts> — the equivalent of Blueprints handing you an Impure node by default: it reads, it writes, and it queues up on the white wire like everyone else. What you have to actively apply for is anything purer than the default (<computes>) or anything more special (<decides>, <suspends>).
One misconception to clear up while we're here: <computes> does not mean the result gets cached. As section 5 of the main lesson covered, Verse computes it however many times you wrote it. A specifier describes what the function is allowed to do, not how the engine will optimize it. If a value is used twice, you still have to name it.
4. The Big Difference: This Is a Contract, Not a Comment
The value of specifiers isn't in typing a few extra words — it's that the compiler checks them for you. Three concrete consequences:
- Try to
seta variable inside a<computes>function? Compile stops you dead — you promised it wouldn't touch state. Whereas a Blueprint function ticked Pure that quietly Sets a variable doesn't even make the engine blink. - Call a
<decides>function without putting it somewhere failure is allowed, like anif? Also rejected. That baffling "failure context" error from Lesson 8 has its roots right here. - Effects propagate upward: a function cannot call a function with "bigger" effects than its own. That's why
OnBegincarries<suspends>— it may call something that waits across frames, and that fact has to be declared in the signature.
Put another way: Blueprints draw "what this node does" into the node's appearance and rely on discipline and your eyes; Verse writes it into the signature and relies on the compiler. Open an unfamiliar .verse file and you don't have to read the body — one line of signature tells you whether that function changes state, whether it can fail, and whether it can stall for a few frames. In Blueprints, that information either requires opening the function graph and reading through it, or is unavailable entirely. Documentation became a contract.
There's a cost, naturally: another vocabulary to learn, and early on you'll be stopped by a pile of "this effect isn't allowed here" errors. The good news is that nearly all of those errors point at a real design question — they're asking what you actually want this function to do, and Blueprints never made you answer that.
5. Quick Quiz
A Blueprint Pure node (no execution pins, recomputed per wire, computes without modifying) is closest to which Verse specifier?
Sources
Compiled from Epic's official documentation and the official language book: Verse Glossary (official docs) ↗ · Specifiers and Attributes in Verse (official docs) ↗ · Book of Verse — Effects (official language book) ↗. For the full list of specifiers and their exact semantics, defer to the official documentation.