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)
-- lua script
-- Single parameter
local double = function(x) return x * 2 end
local result = double(5) -- 10
print("Double:", result)
-- Multiple parameters
local add = function(a, b) return a + b end
local sum = add(3, 4) -- 7
print("Sum:", sum)
-- Block body for multiple statements
local calc = function(a, b)
local temp = a + b
return temp * 2
end
local 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)
-- lua script
-- Closure captures 'offset' from outer scope
local makeAdder = function(offset)
return function(x) return x + offset end
end
local addFive = makeAdder(5)
local 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"
-- lua script
local greet = function(name, greeting)
greeting = greeting or "Hello"
print(greeting, name)
end
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)
-- lua script
local createRange = function(start_val, end_val, step)
step = step or 1
local tbl = {}
for i = start_val, end_val, step do
table.insert(tbl, i)
end
return tbl
end
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)
-- lua script
local angle = 67
local side = angle > 45 and "steep" or "shallow"
print("Side is", side)
-- Chain for multiple conditions
local score = 34
local grade = score >= 90 and "A"
or score >= 80 and "B"
or score >= 70 and "C"
or "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)
gear = 96
cond(gear == 96,
() => print("Standard 96 gear"),
() => print("Non-standard gear")
)
-- lua script
local cond = function(condition, true_thunk, false_thunk)
if condition then true_thunk() else false_thunk() end
end
local gear = 96
cond(gear == 96,
function() print("Standard 96 gear") end,
function() print("Non-standard gear") end
)
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)
-- lua script
local angles = {40, 42, 44}
local map = function(tbl, cb)
local res = {}
for i, v in ipairs(tbl) do res[i] = cb(v) end
return res
end
local radians = map(angles, function(a) return math.rad(a) end)
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)
-- lua script
local values = {12, 5, 8, 20, 3}
local filter = function(tbl, cb)
local res = {}
for _, v in ipairs(tbl) do
if cb(v) then table.insert(res, v) end
end
return res
end
local big = filter(values, function(v) return v > 10 end)
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)
-- lua script
local nums = {1, 2, 3, 4}
local reduce = function(tbl, cb, initial)
local acc = initial
local start_idx = 1
if acc == nil then
acc = tbl[1]
start_idx = 2
end
for i = start_idx, #tbl do
acc = cb(acc, tbl[i])
end
return acc
end
local total = reduce(nums, function(acc, n) return acc + n end, 0)
print("Total:", total)
local largest = reduce(nums, function(a, b) return a > b and a or b end)
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"))
-- lua script
local points = {Point(0,0,1), Point(0,1,0), Point(1,0,0)}
for _, p in ipairs(points) do
show(p, "cyan")
end
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)
-- lua script
local readings = {40.2, 41.8, 40.5}
local mean = (readings[1] + readings[2] + readings[3]) / 3
print("Mean:", mean)
local pts = {Point(0,0,0), Point(10,0,0), Point(5,10,0)}
local center = (pts[1]:add(pts[2]):add(pts[3])):div(3)
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]
print("stack:", stack)
print("pushed:", pushed)
print("popped:", popped)
print("shifted:", shifted)
print("unshifted:", unshifted)
-- lua script
local clone = function(tbl)
local res = {}
for i, v in ipairs(tbl) do res[i] = v end
return res
end
local push = function(tbl, val)
local res = clone(tbl)
table.insert(res, val)
return res
end
local pop = function(tbl)
local res = clone(tbl)
table.remove(res)
return res
end
local shift = function(tbl)
local res = clone(tbl)
table.remove(res, 1)
return res
end
local unshift = function(tbl, val)
local res = clone(tbl)
table.insert(res, 1, val)
return res
end
local stack = {1, 2}
local pushed = push(stack, 3)
local popped = pop(stack)
local shifted = shift(stack)
local unshifted = unshift(stack, 0)
print("stack:", stack)
print("pushed:", pushed)
print("popped:", popped)
print("shifted:", shifted)
print("unshifted:", unshifted)
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)
-- lua script
local concat = function(t1, t2)
local res = {}
for _, v in ipairs(t1) do table.insert(res, v) end
if type(t2) == "table" then
for _, v in ipairs(t2) do table.insert(res, v) end
else
table.insert(res, t2)
end
return res
end
local part1 = {1, 2}
local part2 = {3, 4}
local all = concat(part1, part2)
print("Combined:", all)
local mixed = concat(part1, 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)
-- lua script
local indices = {}
for i = 0, 96, 12 do
if i ~= 0 then
table.insert(indices, i + 3)
end
end
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)
-- lua script
local angle = (function()
local base = 40
local offset = math.sin(math.pi / 4) * 2
return base + offset
end)()
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)
-- lua script
name("Functional Demo")
gear(96)
TierAD("P1", 0, 41.8, 0.18, "x8")
TierAD("G1", 0, 90, 1.0, "x16")
local crownAngles = {35, 28, 22, 15}
for i, angle in ipairs(crownAngles) do
local tier = "C" .. i
print(tier, "at", angle, "°")
end
local sum = 0
for _, a in ipairs(crownAngles) do sum = sum + a end
local avg = sum / #crownAngles
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.