Functional Core
Convex-λ is a functional language. Functions are first-class citizens, meaning they can be assigned to variables, passed as arguments, and returned from other functions.
Lambda Expressions
Lambdas are anonymous functions defined using the => arrow syntax.
Basic Syntax
// Identity function
id = (x) => x
assert(id(5) == 5, "Identity")
// Squaring function
sq = (x) => x * x
assert(sq(4) == 16, "Square")
// Multiple arguments
add = (a, b) => a + b
assert(add(2,3) == 5, "Add")
Closures
Lambdas can "capture" variables from their surrounding scope. This is called a closure.
factor = 10
multiplier = (x) => x * factor
res = multiplier(5) // 50
assert(res == 50, "Closure")
Higher-Order Functions
These are functions that take other functions as input.
Map |>
Transforms every element in an array using a function.
nums = [1, 2, 3, 4]
// Square every number
squares = nums |> (x) => x * x
// Result: [1, 4, 9, 16]
assert(squares[3] == 16, "Map Square")
// Create Points from numbers
points = nums |> (z) => Point(0, 0, z)
// Result: [Point(0,0,1), Point(0,0,2), ...]
assert(points[0].z == 1, "Map Point")
Filter ?|
Selects elements that satisfy a boolean condition.
nums = 1..10
// Keep only evens
evens = nums ?| (n) => n % 2 == 0
// Result: [2, 4, 6, 8, 10]
assert(evens.length == 5, "Filter Even")
// Filter geometry
all_pts = [Point(0,0,1), Point(0,0,-1)]
upper_pts = all_pts ?| (p) => p.z > 0
assert(upper_pts.length == 1, "Filter Geo")
Fold
Reduces an array to a single value by iterating through it.
Syntax: arr.fold(reducer_lambda, initial_value)
nums = [1, 2, 3, 4]
// Sum
sum = nums.fold((acc, x) => acc + x, 0) // 10
assert(sum == 10, "Fold Sum")
// Product
prod = nums.fold((acc, x) => acc * x, 1) // 24
assert(prod == 24, "Fold Prod")
// Find Max
max_val = nums.fold((acc, x) => max(acc, x), 0)
assert(max_val == 4, "Fold Max")
Nested Lambdas & Currying
You can return functions from functions.
// A function that returns a function
make_adder = (n) => (x) => x + n
add_10 = make_adder(10)
res = add_10(5) // 15
assert(res == 15, "Curry")
Complex Chains
Functional programming allows you to build complex logic by chaining simple operations.
Example: Geometry Generation pipeline
- Generate range of angles.
- Filter out specific sectors.
- Map to Vectors (Normals).
- Bind to Planes.
// 1. Generate angles 0..360 in steps of 15
angles = (0..23) |> (i) => i * 15
// 2. Filter: Keep only angles in the first quadrant (0-90)
quad1 = angles ?| (a) => a >= 0 && a <= 90
// 3. Map: Create Normal vectors at 45 deg elevation
normals = quad1 |> (az) => Normal(45, az)
// 4. Map: Create Planes at distance 10
planes = normals |> (n) => Plane(n, 10)
// Render all
_ := planes
assert(planes.length == 7, "Chain Result")
Example: Nested Processing
Finding the average height of points in a point cloud.
cloud = [Point(1,1,5), Point(2,2,10), Point(3,3,15)]
// Extract Z values
heights = cloud |> (p) => p.z
// Sum them up
total_h = heights.fold((acc, h) => acc + h, 0)
// Calculate average
avg_h = total_h / heights.length
assert(avg_h == 10, "Avg Height")