← JamDojo Why Functions

Why Functions

Functions: The Building Blocks

Before we dive into patterns, let’s talk about functions. Everything in Strudel is built from functions, and understanding them unlocks the whole system.


A Function is a Machine

Put something in, get something out:

const addSeven = x => x + 7
addSeven(3)   // → 10
addSeven(10)  // → 17

The x => x + 7 syntax creates a function:

  • x is the input (whatever you pass in)
  • x + 7 is the output (what comes back)

In Strudel, you’re constantly using functions:

note("c4")       // string → pattern
.sound("piano")  // pattern → pattern
.fast(2)         // pattern → pattern

Each operation takes something and returns something new. Chain them together, and you build complex behavior from simple pieces.


Functions Can Take Functions

Here’s where it gets interesting. A function can receive another function as input:

n("0 2 4 6".fmap(x => x + 7))
.scale("C4:major").sound("piano")

The .fmap() function takes your function (x => x + 7) and applies it to every value inside the pattern.

Change the function, change the result:

// Double each value
n("0 2 4 6".fmap(x => x * 2))
.scale("C4:major").sound("piano")
// Conditional: only shift high notes
n("0 2 4 6".fmap(x => x > 3 ? x + 7 : x))
.scale("C4:major").sound("piano")

This pattern—passing functions to other functions—is called higher-order functions. It’s the key to reusability.


The Same Idea in JavaScript

If you know JavaScript arrays, you already know this:

// Array.map applies a function to each element
[0, 2, 4, 6].map(x => x + 7)    // → [7, 9, 11, 13]

// Pattern.fmap does the same thing
"0 2 4 6".fmap(x => x + 7)      // → pattern of 7, 9, 11, 13

The operation is identical. Only the container is different—array vs. pattern.

This isn’t a coincidence. It’s a fundamental pattern that appears everywhere in programming.


Functions Can Return Functions

A function can also create and return new functions:

// A function that makes functions
const addN = n => (x => x + n)

const addFive = addN(5)
const addTwelve = addN(12)

addFive(3)    // → 8
addTwelve(3)  // → 15

This is powerful because it lets you create specialized tools from general ones:

// Create reusable transformations
const octaveUp = x => x + 7      // up one octave (in a 7-note scale)
const octaveDown = x => x - 7    // down one octave
const invert = x => 7 - x        // flip around middle

n("0 2 4 6".fmap(octaveUp))
.scale("C4:major").sound("piano")

Try swapping octaveUp for octaveDown or invert.


Composition: Chaining Transformations

When you chain functions, you’re composing them:

const double = x => x * 2
const addOne = x => x + 1

// These are equivalent:
addOne(double(3))                    // → 7
[3].map(double).map(addOne)          // → [7]

The result flows through each function in sequence. In Strudel:

n("0 1 2 3"
  .fmap(x => x * 2)     // double: 0, 2, 4, 6
  .fmap(x => x + 1))    // add one: 1, 3, 5, 7
.scale("C4:major").sound("piano")

Each .fmap() transforms the result of the previous one. This lets you build complex transformations from simple pieces.


Why This Matters for Music

Three key benefits:

Reusability: Define a transformation once, use it on any pattern.

const humanize = x => x + (Math.random() - 0.5) * 1.5

n("0 2 4 6".fmap(humanize))
.scale("C4:major").sound("piano")

The same humanize function works on any melody.

Composability: Chain simple transformations into complex ones.

n("0 1 2 3 4 5 6 7"
  .fmap(x => x % 4)           // wrap to 0-3 range
  .fmap(x => x * 2))          // then double
.scale("C4:major").sound("piano")

Separation: The transformation (what) is separate from the timing (when).

// Same transformation, different rhythms
const transform = x => x + 7

stack(
n("0 2 4 6".fmap(transform)),
n("0 ~ 2 ~".fmap(transform))
).scale("C4:major").sound("piano")

What’s Next?

We’ve seen that functions transform values, and higher-order functions like .fmap() let us transform values inside containers.

But what exactly is a “container”? Why do we wrap values in things like arrays and patterns?

Continue to Why Containers →

You’ve seen functions transform values. Next, you’ll discover why we wrap values in containers—and how this simple idea enables everything Strudel does.