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

Chapter 14: Functional Programming in FSL — Lambdas & Pipelines

PART II · CHAPTER 14 Arrow Functions => · Closures · map / filter / reduce / forEach · Platonic Solids Showcase

Why doesn't FSL use for (int i = 0; i < 8; i++) or while loops?

Because a gemstone specification is naturally a functional pipeline: you start with a sequence of step numbers, gear indices, or angles (Math.range(0, 4, 1)), transform them mathematically, and turn each entry into a facet tier. By keeping arrays immutable and providing clean arrow functions (=>) with .forEach(), .map(), .filter(), and .reduce(), FSL lets you express complex multi-tier cuts—including all of ProFacet's built-in CAM preforms—in a handful of declarative lines.

(And remember: if you ever prefer traditional imperative for and while loops, you can switch to Lua at any time—covered in Chapter 16!)


1. Arrow Functions (=>), Default Parameters & Closures

In FSL, you define a function by listing its parameters in parentheses followed by =>:

  • Single-Expression Function:
    degToTooth = (deg) => Math.round(deg / 360.0 * gear)
    
  • Default Parameter Values:
    makeStepAngle = (step, base = 42.0, delta = 3.0) => base - step * delta
    
  • Multi-Line Block Function ({ ... }) & Dynamic Tier Names (concat): Inside curly braces { ... }, you can execute multiple statements (including cutting tiers!) and use concat(Prefix, k) to generate numbered tier names (P1, P2, C1, C2) dynamically:
    cutStepPair = (k, pavAngle, crownAngle) => {
      concat(P, k) 0 @ pavAngle   : gp() x4
      concat(C, k) 0 @ crownAngle : gp() + girdle x4
    }
    
  • Conditional Expressions (? : and cond): Use the inline ternary operator condition ? trueVal : falseVal for expressions, or cond(condition, () => trueAction, () => falseAction) when branching between actions.

Functions in FSL are first-class closures: they capture variables from their surrounding scope (gear, ri, girdle) and can be passed directly into array methods.


2. Array Pipeline Methods Reference

Given any array (such as [45.0, 42.0, 39.0]) or numeric sequence generated with Math.range(start, end, step), FSL provides chainable functional methods:

Method / HelperExampleWhat It Does
Math.range(start, end, step)Math.range(0, 4, 1)Generates [0, 1, 2, 3] (start inclusive, end exclusive; step defaults to 1).
.forEach((item, idx) => ...)Math.range(0, 3).forEach(i => ...)Calls the function once for each element (ideal for cutting a sequence of tiers!).
.map((item, idx) => ...)Math.range(1, 5).map(k => 38.0 + k * 2.5)Transforms each element and returns a new array of the same length.
.filter((item) => ...)Math.range(0, 96).filter(t => t % 12 == 0)Returns a new array containing only elements where the predicate returns true.
.reduce((acc, item) => ..., init)angles.reduce((sum, a) => sum + a, 0)Folds the array from left to right into a single accumulated value.
.average()angles.average() / pts.average()Computes the mean of an array of numbers, or the 3D centroid of an array of Points.
concat(a, b)concat("P", i + 1) / concat(arr1, arr2)Concatenates strings/identifiers into a dynamic tier name, or joins two arrays.

3. Interactive Example: Generating a Multi-Tier Step Cut with .forEach()

3. Generating Multi-Tier Cuts with Math.range() + concat()

Look how clean it is to generate a 5-Tier Functional Portuguese Cut in both FSL (using Math.range(0, 3).forEach(...) + concat(...)) and Lua (using for i = 0, 2 do ... end + TierAP(...)). Try changing the step decrement 5.0 to 4.5 in the playground below and clicking Run:

name   = "Functional Portuguese Cut"
ri     = 1.76
gear   = 96
cube   = 3
color  = "#38bdf8"

G1 0 @ 90.0 : size x16
P1 0 @ 62.0 : cp() x16
P2 3 @ 57.0 : gp() x16

// Inner pavilion tiers P3..P5 generated via functional pipeline!
Math.range(0, 3).forEach(i => {
  idx   = (i % 2) * 3
  angle = 52.0 - i * 5.0
  concat(P, i + 3) idx @ angle : mp(concat(P, i + 1)) x16
})

C1 0 @ 46.0 : mp(P1) + girdle x16
C2 3 @ 41.0 : gp() x16

// Inner crown tiers C3..C5 generated via functional pipeline!
Math.range(0, 3).forEach(i => {
  idx   = (i % 2) * 3
  angle = 36.0 - i * 5.0
  concat(C, i + 3) idx @ angle : mp(concat(C, i + 1)) x16
})

T 0 @ 0.0 : mp(C4) x1
-- lua script
name("Functional Portuguese Cut")
ri(1.76)
gear(96)
cube(3)
color("#38bdf8")

TierAD("G1", 0, 90.0, size, "x16")
TierAP("P1", 0, 62.0, cp(), "x16")
TierAP("P2", 3, 57.0, gp(), "x16")

-- Inner pavilion tiers P3..P5 generated via Lua loop!
for i = 0, 2 do
  local idx   = (i % 2) * 3
  local angle = 52.0 - i * 5.0
  TierAP("P" .. (i + 3), idx, angle, mp("P" .. (i + 1)), "x16")
end

TierAP("C1", 0, 46.0, mp("P1"):add(girdle), "x16")
TierAP("C2", 3, 41.0, gp(), "x16")

-- Inner crown tiers C3..C5 generated via Lua loop!
for i = 0, 2 do
  local idx   = (i % 2) * 3
  local angle = 36.0 - i * 5.0
  TierAP("C" .. (i + 3), idx, angle, mp("C" .. (i + 1)), "x16")
end

TierAP("T", 0, 0.0, mp("C4"), "x1")

4. Mathematical Showcase: Cutting a Regular Dodecahedron (12 Pentagons)

Because ProFacet is a true 3D half-space polyhedron kernel, you can slice exact Platonic Solids directly from mathematical formulas! A regular Dodecahedron has 12 regular pentagonal faces: a flat top and bottom face at 0°, and two antiprism rings of 5 faces inclined at arctan(2)≈63.4349°, offset by 36° (gear / 10 teeth) on an 80-tooth gear:

name  = "Regular Dodecahedron (12 Pentagons)"
ri    = 2.42
gear  = 80
color = "#a78bfa"

// Distance from center to each pentagonal face
w = 0.75

// Exact dihedral ring inclination: atan(2) ≈ 63.4349°
ringAngle = Math.toDegrees(Math.atan(2.0))
offset    = gear / 10

// Lower hemisphere (Pavilion: bottom pentagon + 5 lower ring faces)
P1 0      @ 0.0       : w x1
P2 offset @ ringAngle : w x5

// Upper hemisphere (Crown: 5 upper ring faces + top pentagon)
C1 0 @ ringAngle : w x5
T  0 @ 0.0       : w x1
-- lua script
name("Regular Dodecahedron (12 Pentagons)")
ri(2.42)
gear(80)
color("#a78bfa")

local w = 0.75
local ringAngle = math.deg(math.atan(2.0))
local offset    = gear() / 10

-- Lower hemisphere (Pavilion)
TierAD("P1", 0,      0.0,       w, "x1")
TierAD("P2", offset, ringAngle, w, "x5")

-- Upper hemisphere (Crown)
TierAD("C1", 0, ringAngle, w, "x5")
TierAD("T",  0, 0.0,       w, "x1")

Next, learn how your script can inspect the live 3D polyhedron at any point during execution—querying table percentage, depth percentage, facet counts, and vertices—in Chapter 15: Inspecting the Virtual Stone — The stone() API.