Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

  1. Generate range of angles.
  2. Filter out specific sectors.
  3. Map to Vectors (Normals).
  4. 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")