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 Programming

FSL embraces a functional style that favors expressions over statements, immutable data flows, and higher-order functions. You won't find for loops or while statements. Instead, you'll reach for map, filter, reduce, and forEach to process arrays—keeping your designs declarative and easy to reason about.

Arrow Functions

Define unnamed (lambda) functions with the arrow syntax:

// Single parameter (parentheses optional if single param)
double = (x) => x * 2
result = double(5)   // 10
print("Double:", result)

// Multiple parameters 
add = (a, b) => a + b
sum = add(3, 4)      // 7
print("Sum:", sum)

// Block body for multiple statements
calc = (a, b) => {
  temp = a + b
  temp * 2           // last expression is returned
}
value = calc(3, 4)   // 14
print("Calc:", value)

Arrow functions capture variables from their enclosing scope (closures), making them perfect for building reusable utilities:

// Closure captures 'offset' from outer scope
makeAdder = (offset) => (x) => x + offset
addFive = makeAdder(5)
result = addFive(10)  // 15
print("Closure result:", result)

Default Parameters

Parameters can have default values that kick in when arguments are missing:

greet = (name, greeting = "Hello") => print(greeting, name)

greet("World")              // prints "Hello World"
greet("World", "Howdy")     // prints "Howdy World"

Defaults are evaluated at call time in the function's scope, so you can reference earlier parameters:

createRange = (start, end, step = 1) => Math.range(start, end, step)

Ternary Expressions

Instead of if/else statements, use the ternary conditional for inline branching:

angle = 67
side = angle > 45 ? "steep" : "shallow"
print("Side is", side)

// Chain for multiple conditions
score = 34
grade = score >= 90 ? "A"
      : score >= 80 ? "B"
      : score >= 70 ? "C"
      : "F"
print("Grade:", grade)

The condition must evaluate to a boolean; non-boolean values raise an error.

Control Flow

While if statements are not supported, you can use the cond function to execute one of two branches. This is useful when you need to perform actions (like print or show) conditionally.

// cond(condition, true_thunk, false_thunk)
cond(gear == 96,
  () => print("Standard 96 gear"),
  () => print("Non-standard gear")
)

The arguments must be functions (thunks) to ensure only the selected branch is executed.

Array Methods

Arrays come with powerful functional methods—no loops required.

map

Transform every element, returning a new array:

angles = [40, 42, 44]
radians = angles.map(a => Math.toRadians(a))
// radians is now [0.698..., 0.733..., 0.767...]
print("Radians:", radians)

filter

Select elements that pass a test:

values = [12, 5, 8, 20, 3]
big = values.filter(v => v > 10)
// big is [12, 20]
print("Big values:", big)

reduce

Fold an array into a single value:

nums = [1, 2, 3, 4]
total = nums.reduce((acc, n) => acc + n, 0)
// total is 10
print("Total:", total)

// Find max
largest = nums.reduce((a, b) => a > b ? a : b)
// largest is 4
print("Largest:", largest)

forEach

Iterate for side effects (returns null):

points = [Point(0,0,1), Point(0,1,0), Point(1,0,0)]
points.forEach(p => show(p, "cyan"))

average

Compute the mean of numbers, or the centroid of points/vectors:

readings = [40.2, 41.8, 40.5]
mean = readings.average()   // 40.833...
print("Mean:", mean)

pts = [Point(0,0,0), Point(10,0,0), Point(5,10,0)]
center = pts.average()      // Point(5, 3.33..., 0)
print("Center:", center)

push / pop / shift / unshift

In FSL, these methods are pure functions. They return a new array with the changes applied, leaving the original array untouched. This differs from JavaScript.

stack = [1, 2]
pushed = stack.push(3)
// stack is still [1, 2]
// pushed is [1, 2, 3]

popped = stack.pop()
// popped is [1] (new array with last element removed)
// stack is still [1, 2]

shifted = stack.shift()
// shifted is [2] (new array with first element removed)

unshifted = stack.unshift(0)
// unshifted is [0, 1, 2]

Merging Arrays

Use the global concat function to combine arrays or values into a new flattened array:

part1 = [1, 2]
part2 = [3, 4]
all = concat(part1, part2)
// all is [1, 2, 3, 4]
print("Combined:", all)

// Mix arrays and values
mixed = concat(part1, 5)
// mixed is [1, 2, 5]
print("Mixed:", mixed)

concat is also used for building string identifiers if no arrays are involved:

name = concat("C", 1) // "C1"

Chaining

Methods return new arrays, so you can chain transformations fluently:

indices = Math.range(0, 96, 12)
  .filter(i => i != 0)
  .map(i => i + 3)
// indices is [15, 27, 39, 51, 63, 75, 87]
print("Chained indices:", indices)

Blocks as Expressions

Braces create a block whose value is its final expression. Use blocks to group related calculations:

angle = {
  base = 40
  offset = Math.sin(Math.PI / 4) * 2
  base + offset
}
// angle is roughly 41.41...
print("Block angle:", angle)

Blocks work anywhere an expression is expected—inside function bodies, ternary branches, or array literals.

Putting It Together

Here's a practical example that generates calculated crown facet angles:

name = "Functional Demo"
gear = 96
size = 1.0

// Define a pavilion with 8-fold symmetry
P1 0 @ 41.8 : 0.18 x8
G1 0 @ 90 : size x16

// Functional generation of crown tiers
crownAngles = [35, 28, 22, 15]
crownAngles.forEach((angle, i) => {
  tier = concat("C", i + 1)
  // Each crown tier meets the previous or girdle
  // (pseudo-code; actual cut would use proper tier syntax)
  print(tier, "at", angle, "°")
})

// Calculate average angle
avg = crownAngles.average()
print("Average crown angle:", avg)

Why Functional?

Functional patterns make gemstone designs more composable and predictable:

  • No hidden state: Each function receives inputs and returns outputs without side effects (except forEach and show).
  • Reusable utilities: Arrow functions and closures let you build libraries of helpers that snap together cleanly.
  • Declarative intent: angles.filter(a => a > 45) reads like a specification, not an algorithm.

When you find yourself reaching for a loop, pause and consider whether map, filter, or reduce expresses your intent more clearly.