Function Composition
What is the via
Operator?
The via
operator is a function composition operator that combines functions from right to left.
/* f via g = compose(f, g) */
/* f via g via h = compose(f, compose(g, h)) */
The via
operator is right-associative and matches mathematical notation where (f ∘ g ∘ h)(x) = f(g(h(x)))
.
/* Define simple functions */
double : x -> x * 2;
increment : x -> x + 1;
square : x -> x * x;
/* Using via composition */
result1 : double via increment 5;
/* Result: 12 (5+1=6, 6*2=12) */
/* Chained via composition */
result2 : double via increment via square 3;
/* Result: 20 (3^2=9, 9+1=10, 10*2=20) */
The key insight is that via
groups from right to left.
/* This expression: */
double via increment via square 3
/* Groups as: */
double via (increment via square) 3
/* Which translates to: */
compose(double, compose(increment, square))(3)
/* With the execution order of: */
/* 1. square(3) = 9 */
/* 2. increment(9) = 10 */
/* 3. double(10) = 20 */
Precedence rules and via
The via
operator has higher precedence than function application:
/* via binds tighter than juxtaposition */
double via increment 5
/* This is parsed as: */
(double via increment) 5
More examples
/* Data processing pipeline */
data : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
is_even : x -> x % 2 = 0;
double : x -> x * 2;
sum : x -> reduce add 0 x;
/* Pipeline using via */
process_pipeline : sum via map double via filter is_even;
result : process_pipeline data;
/* Reads: sum via (map double via filter is_even) */
/* Result: 60 */
You'll note that we don't need to use @
here -- via
is kinda special-cased because it is an ergonomic feature. It can work with function names directly because it's specifically for function composition. Higher-order functions like map
, filter
, and reduce
require explicit function references using @
because they need a way to distinguish between calling a function immediately vs passing it as an argument while via
only ever takes in functions.
A goal with the via
operator is to align with mathematical function composition:
/* Mathematical: (f ∘ g ∘ h)(x) = f(g(h(x))) */
/* Baba Yaga: f via g via h x = f(g(h(x))) */
When to Use via
Use via
when you want:
- Natural reading:
f via g via h
reads as "f then g then h" - Mathematical notation: Matches
(f ∘ g ∘ h)
notation - Concise syntax: Shorter than nested
compose
calls - Right-to-left flow: When you think of data flowing right to left
Don't use via
when:
- You need left-to-right composition (use
pipe
) - You want explicit mathematical style (use
compose
) - You're working with simple function calls (use juxtaposition)
- If you don't wanna
Common Patterns
/* Data transformation pipeline */
data : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
is_even : x -> x % 2 = 0;
double : x -> x * 2;
sum : x -> reduce add 0 x;
/* Pipeline: filter → map → reduce */
process_pipeline : sum via map double via filter is_even;
result : process_pipeline data;
/* Result: 60 (filter evens: {2,4,6,8,10}, double: {4,8,12,16,20}, sum: 60) */
/* Validation chain */
validate_positive : x -> x > 0;
validate_even : x -> x % 2 = 0;
validate_small : x -> x < 10;
/* Chain validations */
all_validations : validate_small via validate_even via validate_positive;
result : all_validations 6; /* 6 > 0, 6 % 2 = 0, 6 < 10 */
/* Result: true */
Debugging via
Chains
To understand execution order, break down the chain:
/* Complex chain: */
result : square via double via increment via square 2;
/* Break it down: */
/* 1. square(2) = 4 */
/* 2. increment(4) = 5 */
/* 3. double(5) = 10 */
/* 4. square(10) = 100 */
/* Result: 100 */