Skip to content

Functional Programming

Functional Programming

The three commitments

  • functions are values — you pass them, return them, and build big ones out of small ones — composition becomes the main tool instead of inheritance or mutation
  • data doesn't change — you make new values instead of updating old ones — once you can't mutate, most FP techniques stop being style choices and become necessities
  • effects are visible, pushed to the edges — I/O, throwing, the clock — the interesting parts, kept out of the middle of the program; some languages/libraries name this in the type system, plain TS does it by convention (a pure core, an effectful shell)

Foundations

  • expressions vs statements — prefer a form that hands back a value (a ternary, a lookup table) over one that requires mutating an outer variable in each branch to observe the result
  • pure function — the two conditions — same input always gives the same output, and the call touches nothing outside itself (no I/O, no mutation, no hidden state)
  • referential transparency — a call can be replaced by its result without changing the program — the reason a pure call's result can be safely cached or hoisted
  • hoisting a call out of a loop — safe only when the call's inputs and output never change between iterations — no dependency on the loop variable, and no hidden randomness/clock
  • idempotence vs purity — different axes — idempotent means repeat calls leave the resource in the same end state; purity means the call touches nothing outside itself. Neither implies the other
  • which HTTP verbs are idempotent by convention — GET, HEAD, PUT, DELETE, OPTIONS are idempotent by convention; POST is not, and PATCH isn't guaranteed either
  • structural sharing — the reason immutable updates are cheap — the new object reuses the untouched subtrees, so cost is proportional to the path changed, not the data size
  • recursion in place of loops; accumulators; tail calls — a loop with a mutable counter becomes a recursive function passing the running total as a parameter; a tail call is a recursive call in return position — V8 has no tail-call elimination, so this is a style idea in JS, not a performance guarantee

Functions as building blocks

  • higher-order function — a function that takes or returns a function
  • closures — a function remembers the variables from the scope it was created in, even after that scope has returned — the mechanism behind partial application, currying, memoization, and private state without a class
  • currying vs partial application — currying turns an n-ary function into n unary ones; partial application fixes some arguments and returns a function of the rest — `bind` is partial application, not currying
  • `compose` vs `pipe` — direction — `pipe(f,g)(x)` is `g(f(x))`, left to right; `compose(f,g)(x)` is `f(g(x))`, right to left
  • argument order is a design decision — data-last (`filter(pred)(list)`) makes a pipeline composable; data-first (`filter(list, pred)`) makes it readable standalone — FP libraries put the data last
  • point-free, and when it stops paying — `users.map(getName)` over `users.map(u => getName(u))` is a win; a long combinator chain with no named intermediate is not
  • the combinators worth naming — `identity`, `constant`, `tap` (run an effect, return the input untouched), `once`, `negate`
  • `reduce` is the general fold — `map` and `filter` are both expressible as a `reduce`; the reverse is not true — but a `reduce` that mutates its accumulator for the whole array should just be a loop instead

Types and data modeling

  • sum type vs product type — a product holds all of its fields at once (a record, a tuple); a sum holds exactly one of several alternatives (a tagged/discriminated union)
  • where `never` exhaustiveness belongs — a closed set of cases argues for an exhaustive union; an open set (e.g. plugin kinds arriving at runtime) argues for an open/registry shape instead
  • the expression problem — a union + a match makes adding an OPERATION cheap and a CASE expensive; a class hierarchy with virtual methods is the exact mirror — cheap case, expensive operation
  • modelling state as a union, not booleans — make illegal states unrepresentable — a tagged union of loading/ok/error rules out the boolean-flags version's impossible combinations (e.g. `loading:true` with both `data` and `error` set)
  • optional field vs union member — three optional fields on one type = eight possible states, most of them nonsense — a union of only the valid shapes allows just the real ones
  • generic functions and what a signature alone tells you — parametricity ("theorems for free") — a function typed `<T>(x: T) => T` can only be identity (or loop/throw), because it has to work for every possible `T`
  • how type inference works, at a high level — the compiler assigns each expression the most general type consistent with how it's used; this is far easier to do well over expression-oriented code than over code full of mutation and control-flow statements

Values that hold other values

  • `Result<T,E>` vs `throw` — expected failures are values (`Result`), bugs are throws — a return type makes failure visible in the signature and forces the caller to handle it
  • `Option`/`Maybe` vs `T | undefined` — TS's union plus strict null checks already does most of what `Option` does, without a wrapper — an `Option` class is usually not worth it in TS
  • the conversion boundary — pick one layer (usually the transport/handler edge) and convert once between throw-style and `Result`-style — mixing both styles everywhere is the real failure mode
  • functor = something with a lawful `map` — `Array`, `Promise`, `Result`, and `Option` all have one — a container you can `map` over without unwrapping it first
  • applicative: combining several independent values — combines several independent wrapped values with a plain multi-argument function (e.g. two validated fields into one `Result<Person,E>`, collecting every failure) — unlike monad, the values don't depend on each other
  • monad = `of` plus `flatMap` — `flatMap` over `map` when the callback itself returns the container, so nesting doesn't accumulate — unlike applicative, each step can depend on the previous one's result
  • why `Promise` is not quite a monad — `.then` auto-flattens whether or not you return a promise, so it's both `map` and `flatMap` at once — and it makes `Promise<Promise<T>>` unrepresentable
  • `flat`/`flatMap` as the practical half — the one place the functor/monad vocabulary pays off in ordinary code — depth argument defaults to 1
  • railway-oriented / short-circuit chains — a chain of steps where the first failure skips the rest — what `Promise` rejection and monadic `flatMap` already give for free
  • the laws, and why the laws are the point — functor: mapping identity changes nothing, two maps compose into one. Monad: left/right identity, associativity. The laws are what let you refactor a chain — split it, reorder, extract a helper — without re-testing the whole pipeline
  • `traverse` and `sequence` — `sequence` flips `Result<T,E>[]` into `Result<T[],E>` — success only if every item succeeded; `traverse` is `map` then `sequence` in one pass

Effects and the real world

  • what counts as a side effect — anything besides computing the return value from the inputs — I/O, the console, mutating something outside its own scope, throwing, reading the clock or a random source
  • functional core, imperative shell — pure decisions in the middle, I/O at the boundary — keep the testable logic separate from where it touches the outside world
  • describing work as a value; eager vs lazy — a plain function call runs immediately; wrapping the same work as a value (a thunk, an `IO`/`Task`) lets you build it up, pass it around, and decide when — or whether — to run it
  • async as just another effect; `Promise` vs `Task` — a `Promise` starts running the moment it's created and caches its one result; a `Task` (`() => Promise<T>`) is a lazy description of the same work — nothing runs until invoked, so it composes, retries, and cancels like any other value
  • error handling without exceptions — an effect that can fail is exactly the case `Result` is for
  • the Reader idea — a function shaped `(config) => result` composes with the others and the config gets supplied once, at the edge — instead of threading it through every function by hand
  • the State idea — each step is a pure function `(state) => [result, newState]`, and the next step receives the state the previous one produced — the state changes, nothing is mutated

Structure and laws

  • Semigroup and Monoid — a Semigroup is a type plus an associative combine operation (order of grouping doesn't matter); a Monoid is a Semigroup with an identity element too (`0` for `+`, `""` for concat, `[]` for arrays) — `reduce` needs exactly a Monoid
  • equality and ordering as values you pass around — instead of a type's built-in `===` or `<`, equality/ordering become ordinary values — a comparator function, an `{equals}`/`{compare}` object — passed as a parameter
  • type classes / traits vs interfaces — an interface is declared by the type itself upfront; a type class (Haskell) / trait (Rust) attaches behavior to a type from the outside, after the fact, without owning its source — ad-hoc polymorphism instead of inheritance
  • property-based testing — assert that a LAW holds for hundreds of randomly generated inputs (associativity, identity, functor/monad laws) instead of asserting specific examples — `fast-check` in TS, `QuickCheck` in Haskell
  • the law count for each structure — the same two ideas (identity, associativity) all the way down, monad just splits identity in two. Category: 2 (identity, associativity). Monoid: 2 (identity, associativity). Functor: 2 (identity, composition). Monad: 3 (two identity laws, one associativity)

In the large

  • data-first design and module boundaries — model the data first, write functions over it second — a module's boundary should export a data shape plus plain functions over it, not a class hiding mutable state
  • eager chain vs lazy pipeline — `arr.filter(...).map(...).slice(0,3)` builds two full intermediate arrays to return three items; a generator pipeline (or iterator helpers) computes three
  • generator as a lazy sequence — an infinite sequence is only expressible lazily
  • a thunk — `() => expensive()` — a value wrapped in a function so it isn't computed until needed
  • short-circuit as laziness — `&&`, `||`, `??` don't evaluate the right side unless they must
  • state over time as a fold over events — current state = `events.reduce(reducer, initialState)` — a pure function over the full history; exactly what a Redux-style reducer is, and the same idea as event sourcing
  • why immutability makes concurrency easier — a value that can never change can be read by multiple threads/workers at once with no lock and no race condition — shared MUTABLE state is what makes concurrency hard
  • the real costs — every immutable update allocates (structural sharing keeps this cheap, not zero); deep recursion without a trampoline risks a stack overflow; unfamiliar style slows a team down before it speeds them up
  • living alongside imperative code — most real codebases are mixed — a pure functional core surrounded by imperative frameworks; the skill is knowing where to draw the boundary, not rewriting everything functional

Optional deep work

  • lenses / optics — composable immutable get/set at depth
  • effect systems — the "effects as values" idea taken all the way — a type like `Effect<R,E,A>` tracks dependencies, possible errors, and result all in the type; Task + Reader + Result fused into one
  • free monads — separate DESCRIBING a sequence of effectful steps (build a data structure representing the program) from INTERPRETING them (run one or more interpreters over it later)
  • dependent types — a type that depends on a VALUE, not just another type — e.g. a vector type parameterized by its own length; TS doesn't have these
  • category theory — the branch of math functor/monad/applicative borrow their vocabulary and laws from — objects and structure-preserving maps (morphisms), composed associatively with an identity
  • transducers — composing `map`/`filter` steps into one pass with no intermediate arrays
  • trampolining, and why — V8 has no tail-call elimination, so deep recursion throws `RangeError` — a trampoline (or a loop) is the fix
  • immutable-data libraries — Immer's draft-mutate-produce shape, and where a persistent-collection library earns its weight

Vocabulary

  • total function vs partial function — a total function is defined for every input in its domain; a partial one isn't — array `head` on an empty array, integer division by zero
  • referential opacity — the opposite of referential transparency — a call that can't be replaced by its result without changing the program (reading the clock, a counter)
  • arity — the number of arguments a function takes — unary, binary, n-ary; a variadic function takes any number
  • bifunctor — a functor with two independent type parameters you can map over separately — e.g. `Result<T,E>` having both `map` (over `T`) and `mapErr` (over `E`)
  • desugaring — rewriting convenient syntax (a `for` loop, `async`/`await`) into the more primitive form it's shorthand for (a fold, a chain of `.then`s)
  • composition (the general word) — combining two things into one bigger thing of the same kind, without either needing to know about the other — `compose`/`pipe` is the function-level case, Semigroup/Monoid is the value-level case
  • record of functions — bundles related pure functions into a plain record instead of an OOP object holding hidden state — the standard FP shape for dependency injection, decoupling logic from the infrastructure it calls

Vocab: Intro

  • functional programming — programming built around pure functions, immutable data, and composition, instead of step-by-step commands that change state
  • imperative programming — programming as a sequence of commands that change state one step at a time
  • object-oriented programming — organizing code around objects that bundle state and behavior together
  • mutable state — data that can be changed in place after it's created, rather than replaced with a new value
  • assignment-based programming — programming where progress is tracked by reassigning variables as you go — the imperative default FP moves away from
  • side effects — anything a function does besides computing its return value from its inputs — I/O, mutating something outside itself, throwing, reading the clock or randomness
  • DRY — avoid duplicating the same logic in more than one place
  • YAGNI — don't build for a requirement you don't actually have yet
  • loose coupling, high cohesion — modules should depend on each other as little as possible, while what's inside one module should belong together
  • principle of least surprise — a design should behave the way someone would reasonably expect it to, given its name and shape
  • single responsibility — a unit of code should have one reason to change
  • associativity — grouping doesn't matter: `(a+b)+c` equals `a+(b+c)`
  • commutativity — order doesn't matter: `a+b` equals `b+a`
  • identity (property) — a value that combines with anything and leaves it unchanged — `0` for `+`, `1` for `×`
  • distributivity — one operation spreads over another: `a×(b+c)` equals `a×b + a×c`
  • composable parts — small pieces built so they combine into bigger pieces without needing to know about each other's internals
  • set theory — the math of collections and membership; underlies "a type is the set of values that satisfy it"
  • reasoning about code — predicting what code does by reading it alone, without running it or tracking hidden state — the actual payoff FP is chasing

Vocab: First Class Functions

  • first class functions — functions can be stored in variables, passed as arguments, and returned from other functions, just like any other value
  • callable — anything invokable with `()` — a plain function, a bound function, an object with a call signature
  • callback — a function passed into another function to be invoked later, usually when some event or step completes
  • delayed evaluation — wrapping work in a function so it doesn't run until that function is actually called
  • indirection — adding a layer between a caller and the real work so the caller doesn't need to know the concrete implementation
  • wrapper function — a function that calls another function, adding behavior around it (logging, timing, validation) without changing the original
  • leaky abstraction — an abstraction meant to hide details that forces the caller to know about them anyway
  • this binding — what `this` refers to depends on how a function is called, not where it's defined — except arrow functions, which capture it lexically
  • generic vs. specific code — generic code works across many types/shapes at the cost of doing less per-case; specific code does more but only for one shape
  • reusability — writing something once so it applies in more than one place without being copied
  • naming / misnomers — a name that no longer matches what the thing actually does, misleading anyone reading it
  • micro-optimization — tuning a small, rarely-hot piece of code for performance, usually not worth the readability cost

Vocab: Pure Functions

  • pure function — same output for the same input, and no observable effect outside itself
  • impure function — depends on or affects something outside its own inputs/outputs — state, I/O, randomness, the clock
  • mutation — changing a value in place rather than creating a new one
  • immutability (Object.freeze) — preventing a value from being changed after creation; `Object.freeze` is JS's shallow, runtime version of it
  • system state — the sum of all mutable data a program currently holds, spread across variables, objects, and external stores
  • cognitive load — how much a reader has to hold in their head to understand code; pure functions lower it because nothing hidden can affect the result
  • system complexity — how hard a system is to reason about as a whole, driven up by hidden state and interactions between parts
  • mathematical function — a pure function models exactly this: one input always maps to the same one output, nothing else involved
  • caching — storing a computed result so a repeated call can reuse it instead of recomputing
  • memoization — caching keyed by a function's arguments; only safe when the function is pure
  • portability — pure code doesn't depend on its ambient environment, so it can be moved or reused elsewhere unchanged
  • self-documenting code — code whose behavior is clear from its own names and shape, without needing external comments
  • explicit dependencies — everything a function needs arrives through its parameters, not through hidden globals or ambient state
  • dependency injection — supplying a function/object what it needs from outside, rather than it reaching out to construct or fetch it itself
  • parameterization — turning something that was hardcoded or reached-for into a parameter instead
  • testability — how easy code is to test in isolation; pure functions are trivially testable since there's no hidden state to set up or tear down
  • property-based testing — testing that a general law holds for many randomly generated inputs, rather than checking specific examples — `fast-check` in TS, `jqwik` in Java
  • referential transparency — a call can be replaced by its result without changing the program's behavior
  • equational reasoning — treating code like algebra, substituting equals for equals — safe only because referential transparency guarantees it
  • inlining / substitution — replacing a call site with the function's body — only valid to do freely under referential transparency
  • parallel code — code split to run at the same time across cores, threads, or workers
  • shared memory — memory more than one thread/worker can read and write — the source of most concurrency bugs
  • race condition — a bug where the outcome depends on timing between concurrent operations touching shared mutable state

Vocab: Currying

  • currying — turning an n-ary function into a chain of n unary functions, each taking one argument and returning the next
  • closure — a function that remembers the variables from the scope it was created in, even after that scope has returned
  • partial application — fixing some of a function's arguments now and getting back a function of the rest
  • higher order function — a function that takes another function as an argument, returns one, or both
  • argument ordering (data last) — putting the data being operated on as the last parameter, so earlier arguments can be fixed first to build a reusable step
  • unary function — a function that takes exactly one argument

Vocab: Coding by Composing

  • function composition (compose) — combining two functions into one, feeding one's output into the other's input; `compose(f,g)(x)` is `f(g(x))`
  • variadic function — a function that accepts any number of arguments
  • right-to-left data flow — `compose`'s reading order: the rightmost function runs first, matching mathematical `f(g(x))` order
  • associativity of composition — grouping doesn't matter when composing more than two functions: `compose(f, compose(g,h))` equals `compose(compose(f,g), h)`
  • extract function (refactoring) — pulling a piece of logic out into its own named function, usually to make it reusable or composable
  • pointfree style — defining a function without naming its arguments, built purely by composing other functions
  • identity function (id) — `x => x` — the do-nothing function that acts as composition's identity element
  • trace / debugging composition — inserting a `tap`-like logging step into a composed pipeline to see an intermediate value without changing the result
  • types as sets — treating a type as the set of all values that satisfy it, so type relationships mirror set relationships (subset, union, etc.)

Vocab: Declarative Coding

  • declarative coding — describing WHAT the result should be, leaving HOW to compute it to the underlying implementation
  • imperative coding — describing HOW to get the result, step by step, in the order things should happen
  • expression vs. step-by-step instruction — an expression evaluates to a value; a statement just performs an action and moves on
  • specification (what, not how) — declarative code as a spec of the desired outcome, decoupled from the mechanism that produces it
  • order of evaluation — the sequence in which a language actually evaluates subexpressions; imperative code depends on this being predictable
  • parallel / concurrent computing — parallel runs work at the same literal time across cores; concurrent structures independent tasks that may or may not run simultaneously
  • JIT optimization — a just-in-time compiler watches a program as it runs and compiles hot paths to faster machine code on the fly
  • isolating impure actions (namespacing) — keeping impure code (I/O, mutation) grouped and named separately from pure logic, so the boundary is visible
  • universal getter (prop) — a generic `prop(key)(obj)` function that reads any field off any object, instead of writing a getter per field
  • map's composition law — mapping `f` then `g` gives the same result as mapping the single composed function `g∘f` — justifies fusing multiple `.map()`s into one
  • principled refactor — a refactor justified by an algebraic law holding, not just "it looks the same"

Vocab: Type Signatures

  • type signature — a function's declared shape: the types of its parameters and its return type
  • type inference — the compiler working out a value's type from how it's used, without an explicit annotation
  • type annotation — writing a type explicitly instead of relying on inference
  • compile-time type checking — catching type errors before the program runs, by checking code against its declared/inferred types
  • dynamic language — a language that checks types at runtime instead of compile time — JS without TS
  • type variable — a placeholder standing in for "any type," used in generics — the `T` in `<T>(x: T) => T`
  • polymorphic type — a type that works over more than one concrete type via a type variable
  • parametricity — a function typed generically enough can't inspect or special-case its type parameter — "theorems for free"
  • free theorem — a property you get for free purely from a generic signature, without looking at the implementation
  • rewrite rule — a compiler optimization that replaces one expression with an equivalent one it knows is safe, e.g. fusing two `map`s into one
  • type constraint — restricting a type variable to only types that satisfy some capability, e.g. `<T extends Comparable>`
  • domain restriction — narrowing which inputs a function accepts, often to make it total instead of partial

Vocab: Tupperware

  • container — a value that wraps another value, giving you a context to operate in — `Array`, `Maybe`, `Result`, `Promise`
  • functor — a container with a lawful `map`
  • map — apply a function to the value(s) inside a container without unwrapping it first
  • of (constructor / lifting) — putting a plain value into a container, minimally wrapped — `Array.of`, `Promise.resolve`
  • Maybe — a container representing a value that might be absent, without using `null`/`undefined` directly
  • Just / Nothing — Haskell's two cases of `Maybe`: `Just x` holds a value, `Nothing` holds none
  • Some / None — Rust/Scala's names for the same two cases as Just/Nothing
  • Option / Optional — Scala/Java's name for the Maybe type — Java's `java.util.Optional`
  • null check / type safety — TS's union `T | undefined` plus strict-null-checks does most of what Maybe/Option gives, checked at compile time
  • short-circuiting — a chain of operations on Nothing/None skips the rest and returns Nothing/None immediately
  • Either — a container with two possible cases, conventionally used for success-or-failure
  • Left / Right — Either's two cases; by convention Left holds an error/failure, Right holds a success value
  • pure error handling — using a container type like Either/Result instead of throwing, so failure is visible in the return type
  • throw / catch — the exception mechanism Either/Result is offered as an alternative to
  • lifting — taking a plain value or function and wrapping it to work inside a container
  • sum type — a type holding exactly one of several alternatives
  • deferred effect — an effect represented as a value/description, not run until something explicitly executes it
  • command pattern — OOP's version of the same idea: wrapping a request/action as an object so it can be queued, logged, or undone
  • queue — a sequence of pending items processed in order, often used to hold deferred effects/commands until they're run
  • Task / Future — a lazy description of an asynchronous computation (fp-ts's `Task`); Java's `Future`/`CompletableFuture` is the eager cousin
  • fork — starting a `Task`/`Future`'s execution — the point where the lazy description actually begins running
  • asynchronous actions — work that completes at some later time rather than immediately
  • promises — JS's built-in eager, cached async value
  • callbacks — a function passed in to be called when async work completes — the mechanism `Promise`/`Task` replace
  • functor laws: identity, composition — mapping `identity` changes nothing; mapping `f` then `g` equals mapping the composed function `g∘f`
  • nested / stacked functors — one functor inside another, e.g. `Promise<Result<T,E>>` — handling requires mapping "through" both layers
  • event streams — a sequence of values arriving over time, instead of all at once — the async analogue of an array
  • observables — a library type (RxJS) representing an event stream you can `map`/`filter`/combine like a lazy, push-based collection

Vocab: Monadic Onions

  • monad — a pointed functor with `chain`/`flatMap` — `of` plus flattening
  • join (flatten) — collapsing one level of nesting: `M<M<T>>` to `M<T>`
  • chain / bind / flatMap — map then join in one step: run a function that itself returns a wrapped value, without ending up double-wrapped
  • nested monads — the `M<M<T>>` shape that `join`/`chain` exists to collapse
  • sequencing of effects — running one effectful step after another, where each may depend on the previous one's result
  • algebraic data types — types built by combining sums and products — TS discriminated unions are exactly this
  • monad transformers — a way to stack two monads together (e.g. handle both "might fail" and "is async" at once) without hand-rolling the combined type each time
  • callback hell / pyramid of doom — deeply nested callbacks from sequencing async steps without composition — the practical problem monadic chaining and `async`/`await` solve

Vocab: Applicative Functors

  • applicative functor — a functor that also has `ap`, letting you combine several wrapped values with a plain multi-argument function
  • ap — apply a wrapped function to a wrapped value: `Container<a→b>` applied to `Container<a>` gives `Container<b>`
  • concurrent vs. sequential evaluation — applicative combines independent values so order doesn't matter and they could run concurrently; monad's `chain` is inherently sequential
  • principle of least power — use the weakest abstraction that gets the job done — functor if you only need `map`, applicative if values are independent, monad only if a step depends on a prior result

Vocab: Natural Transformations

  • principled type conversion — converting between container types (e.g. `Maybe` to `Either`) in a lawful, uniform way rather than ad hoc
  • isomorphism — a pair of conversions between two types that are exact inverses of each other — converting there and back always returns the original value
  • fusion / optimization by law — merging multiple operations (e.g. two `map`s) into one pass, justified by a law guaranteeing the result is identical

Vocab: Traversing the Stone

  • sequence — flip a list of wrapped values into one wrapped list — e.g. a list of `Result`s to a `Result` of a list
  • traverse — `map` then `sequence` in one pass: run an effectful function over every element and combine the results into one wrapped structure
  • type constructor — a type that takes a type parameter to produce a concrete type — `Array` isn't a type by itself, `Array<T>` is
  • effect ordering — whether traversing runs effects one at a time (sequential) or all at once (concurrent) depends on the effect type
  • predicate — a function returning `boolean`, used to test each element — the shape `filter`/`find` take
  • partitioning vs. validating — splitting a list into successes and failures keeps both; validating fails the whole batch on the first failure (or collects all)
  • reduce / accumulator / seed value — `reduce`'s three parts: the combining function, the running total, and its starting value
  • laws as code guarantees — laws are what let you refactor a pipeline without re-testing it by hand, because the guarantee is structural, not empirical
  • encapsulation — hiding a value's internal representation behind an interface, so callers interact with it only through defined operations

Vocab: Monoids

  • monoid — a type plus an associative combine operation plus an identity element
  • semigroup — a type plus an associative combine operation, no identity required
  • concat (associative binary operation) — the conventional method name for a semigroup/monoid's combine operation
  • empty (identity element) — the conventional method name for a monoid's identity value — combines with anything and changes nothing
  • binary operation — an operation taking exactly two inputs of the same type and producing one output of that type
  • closed under an operation — combining two values of a type always produces another value of the same type, never escaping it
  • domain / codomain — a function's domain is the set of valid inputs; its codomain is the set the outputs are drawn from
  • program to an interface, not an implementation — depend on what an operation can do (e.g. "this has a `concat`"), not on a specific concrete type
  • Sum, Product, Min, Max — common numeric monoids: combine is `+`/`×`/`Math.min`/`Math.max`, identity is `0`/`1`/`+Infinity`/`-Infinity`
  • Any, All — boolean monoids: combine is `||`/`&&`, identity is `false`/`true`
  • First — a monoid that keeps the first non-empty value it sees and discards the rest
  • fold — collapsing a structure down to a single value using a combining operation — the general case `reduce` implements for arrays
  • reduce with initial value — supplying the seed explicitly instead of assuming the first element is it — required whenever the array might be empty