02_Function_Composition

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 */