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
forEachandshow). - 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.