Tuple Expansion (Splatting): Pass One Parcel as a Series
Lesson 18 taught you to pack multiple values into a tuple; this page teaches the reverse move: drop one whole tuple parcel into a single function call, and it automatically spreads out, filling several inputs. Behind this officially documented convenience hides one of Verse's deeper designs — a function's input list is, at bottom, a tuple.
1. Express Delivery: No Unpacking — Sign for the Whole Parcel
First, recreate the awkward scene Lesson 18 ended on. You have a function that spits out a tuple(float, float) (a two-slot parcel of floats), and another that needs two float inputs, and you want to feed the former's result to the latter. The way the basics course taught it, you catch the whole package, split it slot by slot, then pass the pieces one at a time:
Point := GetTarget(), then PrintPoint(Point(0), Point(1)) — works, but wordy. The official Tuple in Verse documentation records the prettier way: hand a whole tuple to a function as one argument, and the effect is exactly as if each element in the parcel had been passed as its own separate input. The feature is called tuple expansion, and the community also calls it splatting (the parcel goes "splat" and opens flat):
using { /UnrealEngine.com/Temporary/Diagnostics }
# A function taking two parameters
PrintPoint(X:float, Y:float):void =
Print("Coordinates: ({X}, {Y})")
# A function returning a tuple: multiple values shipped as one package
GetTarget():tuple(float, float) =
(100.0, 200.0)
Demo():void =
Target := GetTarget()
# Tuple expansion: one whole parcel as two arguments, auto-split into place
PrintPoint(Target)
# Fully equivalent to passing them one by one:
PrintPoint(100.0, 200.0)
# You can even ram the return value straight into the call — no middleman variable:
PrintPoint(GetTarget())
Reading the code: PrintPoint is a function with "two float inlets"; GetTarget spits out a two-slot parcel. Inside Demo, all three feeding styles do exactly the same thing: shove the whole Target parcel in, feed the two numbers 100.0, 200.0 separately, or ram GetTarget()'s result straight into the call. When the whole parcel goes in, Verse slaps it open automatically and slots the pieces into those two inlets by position — like wiring a "multi-output-pin" node into the next "multi-input-pin" node as one whole bundle.
PrintPoint(Target) looks like "handing just one value to a function that wants two inlets," yet it Compiles cleanly and runs right — because Target's type tuple(float, float) matches the inlet list (X:float, Y:float) shape for shape, so Verse slaps the parcel open and the elements file into place by position. And with that, the pipeline "one function's several outputs straight into the next function's several inlets" stands: upstream packs and ships, downstream signs for the whole parcel, and not one line of unpacking code sits in between.
2. The Worldview Underneath: Every Call Passes One Tuple
This isn't a convenience that happens to work; it flows naturally from a deep piece of Verse design: a function's input list is, at bottom, a tuple. Writing PrintPoint(X:float, Y:float) is, in the language's eyes, roughly saying "this function takes one tuple(float, float)"; writing PrintPoint(100.0, 200.0) is roughly "hand it the two-slot parcel (100.0, 200.0)." So "hand over a ready-made parcel whole" naturally works too — the three spellings converge because they were the same thing all along.
The same lens explains two "coincidences" from Lesson 18: why do tuples and function calls look exactly alike — parentheses plus commas? And why is the official stance for "one function spitting out several values," of all things, spitting out a tuple? One answer covers both: in Verse's model, a function is a machine that "eats one tuple and emits one value (which may itself be a tuple)." Inputs, return values, packing, unpacking — all different profiles of the same tuple machinery.
Two practical scenarios. First: argument forwarding. When building a wrap-around function, hand the inputs you received to the inner function untouched — that string of inputs can itself be caught as one tuple and passed on whole. Second: function chaining. Make a batch of utility functions all "eat tuple, spit tuple," and they link head-to-tail like a conveyor belt: StepB(StepA()) — the several results A spits out pour straight into B's several inlets, without one line of glue code in between.
One last note: the auto-spread only happens when "the shapes line up." If a function's one and only inlet is itself of tuple type, then the tuple you hand over is that inlet's value — it won't be pried open. The compiler matches by type, not by guesswork.
Given a function F(X:int, Y:int) that wants two integer inputs, and a two-slot parcel T:tuple(int, int) = (1, 2), what happens when you hand it the whole parcel, F(T)?
Sources & Further Reading
Compiled from official Epic documentation: