Programming Paradigms

Context: FIT2102_MOC · the unit’s spine — a paradigm is a model for computation, so the differences are semantic, not cosmetic · the ladder it climbs is Levels of Abstraction (Machine to High-Level) · the JavaScript machinery is JavaScript Functions as Values

Quick Revision

  • 🎯 Objective: imperative = tell the computer how, step by step; declarative = describe what you want ➔ same result, different model of computation.
  • 📦 Core Components: imperative ➔ mutable state + statements | declarative/functional ➔ pure expressions + recursion/HOFs | OO ➔ composition via objects.
  • ⚡ Key Constraint: declarative code buys referential transparency (an expression can be replaced by its value) at the cost of a stack-overflow ceiling on deep recursion — neither style is free.

📝 How It Works

1. What a paradigm is

  • A model for computation ➔ not a syntax family. Most modern languages support several paradigms; languages differ in which ones they support well.
  • The four in scopeimperative · declarative · functional · object oriented, plus the orthogonal axis of strong vs dynamic type systems.
  • Imperative ➔ tell the computer how to compute, step by step. Its native model is von Neumann/Turing: an instruction pointer walking a sequence of state mutations.
  • Declarative ➔ describe what you want, not how to get it. Its native model is the lambda calculus: expressions reduced to values, no instruction pointer, no store.
  • Where JavaScript sits ➔ basically an imperative language with C/Java-like syntax, except that it is interpreted and functions are objects assignable to variables — which is why heavy functional style is available in it at all.

2. Purity and referential transparency

  • Pure expression ➔ has no effects outside the expression. No mutation, no I/O, nothing observable but its value.
  • Referential transparencyfollows from purity: any expression may be replaced by its value without changing the program’s meaning. This is what makes equational reasoning (and later, Functor/Monad laws) possible.
  • Declarative ⟹ closer to the definitionsumTo n = n + sumTo(n-1) is the inductive definition of the sum, not a recipe for computing it; the code states the loop invariant instead of maintaining it.
  • The caveat, stated honestly ➔ too many levels of recursion causes a stack overflow. Declarativeness is not free.

3. Why the unit distrusts hand-coded loops

  • for ([init]; [cond]; [final]) statement has four independent failure surfaces ➔ the init can start at the wrong value (n instead of n-1, 1 instead of 0) or initialise the wrong variable; the condition can use = for ==/===, <= for <, or test the wrong variable; the final-expression can increment the wrong variable; the body can mutate the very variable the termination test reads, because it is in scope.
  • while, goto and recursive loops carry risks too ➔ the slides’ conclusion is not “prefer while” — it is stop hand-coding iteration.
  • The replacement ➔ built-in higher-order Array methods (forEach/map/filter/reduce), which encode the traversal once and correctly ➔ JavaScript Functions as Values.
  • Composition, two ways ➔ programs can be composed through higher-order functions (behaviour as a parameter) or through objects (behaviour bundled with state) — the unit contrasts these deliberately rather than ranking them.

🧬 Evaluation Model

One computation — sum the numbers up to — climbing from imperative to functional. Every version returns the same value; only the model changes.

// 1. IMPERATIVE, truthiness guard: two mutations per step
function sumTo(n) {
  let sum = 0;
  while (n) { sum += n--; }        // Boolean(0) === false ends the loop
  return sum;
}
 
// 2. IMPERATIVE, counted loop: the four failure surfaces above live here
function sumTo(n) {
  let sum = 0;
  for (let i = 1; i <= n; i++) { sum += i; }
  return sum;
}
 
// 3. DECLARATIVE, recursive: no mutable variables, every expression pure
function sumTo(n) {
  if (n === 0) return 0;           // base case
  return n + sumTo(n - 1);         // inductive step
}
 
// 4. DECLARATIVE, condensed + generalised by a HOF parameter
function sumTo(n, f) { return n ? f(n) + sumTo(n - 1, f) : 0; }
sumTo(10, x => x * x);             // 385  -- behaviour is now an argument
  • Desugaringn ? a : bif (n) { return a } else { return b }; x => x * xfunction(x) { return x * x }.
  • Type signature ➔ version 4 is — taking a function is what makes it higher-order.
  • What changed between 2 and 3let disappears. No binding is ever reassigned, so no expression’s value depends on when it is evaluated.

⚖️ Core Decision Matrix

ParadigmYou writeModel of computationBuys youCosts you
Imperativestatements mutating statevon Neumann / Turingdirect match to hardware; bounded memoryfour loop failure surfaces; no referential transparency
Declarativeexpressions describing the resultreduction of expressionsstates the invariant; safe to reason about equationallyindirection from the machine
Functionalpure functions + HOFslambda calculuscomposability; behaviour as a parameterstack overflow on deep recursion
Object orientedobjects bundling state + behaviourmessage passing over encapsulated statecomposition via interfaces; locality of statestate is mutable by design ➔ harder to reason about

When It Flips: prefer the imperative loop only when recursion depth is unbounded or the mutation is the point (in-place algorithms). Otherwise the built-in Array HOFs win: they remove all four hand-coded-loop failure surfaces at once, and they compose (map(…).reduce(…)) where loops do not.

🧠 Active Recall