Computational Literacies Lab

0002.g Paradigms

On this page
  1. Writing down the steps
  2. Writing functions instead of changing state
  3. Writing down the result or the rules
  4. When code runs: event-driven programming
  5. A different question: compiled and interpreted

The algorithms in the last section all told the computer what to do, one step at a time, and changed variables along the way. A programming paradigm is a general style of organizing a program, and that step-by-step style is only one of them. The paradigms are easier to keep straight when grouped around one question: what does the programmer write down? One family writes down the steps, adding more organization as it goes from imperative to procedural to object-oriented programming. Functional programming also spells out a computation, but it builds that computation from functions that return values, and it avoids changing state. A second family writes down the desired result or the facts of the matter and leaves the steps to the system, which describes declarative and logic programming. Event-driven programming answers a different question, which is when code runs. A single language often supports several of these styles. Python, for instance, supports more than one, so a paradigm describes how a program is written more than it labels a language.

Writing down the steps#

Imperative programming expresses a program as a sequence of statements that change the program's state, the values it is holding at a given moment. A recipe is a good comparison: heat the pan, add the oil, add the onions, with the food changing at each step. Reassignment is the signature feature of this style, as in total = total + 4.1 Everything in 0002.f can be read as imperative code.

As imperative programs grow, a long list of instructions becomes hard to manage, and the next two styles add structure while keeping the steps. Procedural programming is imperative code organized into procedures and blocks, with scope limiting where names are valid.2 A typical procedural program is a main routine that calls named procedures in order. Each procedure's local variables exist only inside it, which is why a name declared in one function can't be read from another, while different functions are free to reuse the same name. The procedures can still change shared data, so this remains an imperative style.

Object-oriented programming organizes a program differently, by modeling a problem as objects that bundle data with the methods that operate on it, each object holding its own state.3 Two turtles, t1 and t2, each track their own position, and t1.forward(50) moves only the first. A game with many characters fits this well, with a Character class defining shared behavior and one object for each character's own health and position. A clear way to see the shift from procedural programming is to take a global balance and a deposit() procedure and move both into an Account class, from which you create account objects. Renaming things or adding comments wouldn't change the paradigm, because it is the bundling that matters. 0002.h and 0002.l return to classes in more detail.

Writing functions instead of changing state#

Functional programming departs from the others on the point that imperative programming holds central, by avoiding changes to state. Programs are built by composing functions that return values, with an emphasis on immutable data and avoiding side effects, which are changes a function makes beyond returning its result. Where an imperative program reassigns a variable in a loop, a functional one might use recursion and never reassign anything. Functions such as map(square, values) produce new data, here a new array of squares, and leave values unchanged. A function that prints or changes shared data isn't pure, even if it returns the right value: return x * 2 alone is pure, and adding log.append(x) or output(x) before it isn't. Purity has practical payoffs.45 A pure function returns the same output for the same input and changes nothing else, so pure functions are simple to test, and they simplify concurrency, because they avoid the problems of shared, changing state.

Writing down the result or the rules#

The second family stops describing a process. Declarative programming describes the desired result rather than the steps for producing it, as in SQL, HTML, and CSS. SELECT name FROM Students WHERE grade = 10 says which rows are wanted and doesn't say how to look through the files, whereas an imperative version would loop over the rows and test each one. Likewise, <h1>Welcome</h1> and h1 { color: blue; } state what should be true of a page and leave the browser to draw it. The system chooses how, which is why a query optimizer can rewrite a query to run faster without changing its results. It has that freedom only because the query never specified any steps.

Logic programming takes this further by having the programmer state what is true. A program is facts and rules, and the system answers queries by inference, as in Prolog:

parent(ana, ben).
parent(ben, cara).
grandparent(X, Z) :- parent(X, Y), parent(Y, Z).

Asked whether grandparent(ana, cara) holds, the system works out the answer from the facts and the rule, with no loop written by anyone. This suits constraint problems, such as checking whether a schedule meets many stated conditions, since the constraints can change without anyone rewriting a search procedure. Logic programming and SQL are related in spirit, because both leave the method to the system. They differ in that a SQL query retrieves and changes stored data, whereas a logic program reasons from rules.

When code runs: event-driven programming#

The styles above mostly assume that a program decides for itself what happens next, but many programs have to wait. In event-driven programming, handlers run in response to events such as clicks and messages, a pattern common in user interfaces and block-based environments, where a "when this sprite is clicked" block starts its own stack of instructions.6 It suits programs whose input arrives at unpredictable times, such as a chat application. Two kinds of mistake follow directly from how it works. A handler must be registered rather than called at load time, so a button's message that appears as soon as the page loads has probably been run directly and not attached to the click. And because event order isn't fixed, shared variables can interact unexpectedly, as when two handlers both change a game's score.

A different question: compiled and interpreted#

One further distinction is often mistaken for a paradigm. Compiled languages are translated to machine code before running, which allows optimization for the target hardware and can catch errors early, though it can't guarantee that no errors will occur while the program runs. Interpreted languages are executed by another program as they run, so an error appears only when execution reaches it: a misspelled name on line 200 goes unnoticed until the program gets there. This is a feature of the implementation and has nothing to do with how a program is organized, so a phrase like "interpreted paradigm" confuses two different things. Many implementations mix both, as Python does by compiling source code to bytecode that another program then executes.

Whatever their style, all of these programs work on data, and each paradigm's descriptions have relied on variables, arrays, and objects. The next section looks closely at those building blocks.

  • programming paradigm: a general style of organizing a program
  • imperative: statements that change the program's state in sequence
  • procedural: imperative code organized into procedures and blocks, with scope
  • object-oriented: objects that bundle data with the methods that operate on it
  • functional: composing functions that return values, with immutable data and no side effects
  • pure function: a function that returns the same output for the same input and changes nothing else
  • declarative: describing the desired result rather than the steps, as in SQL, HTML, and CSS
  • logic: facts and rules, with queries answered by inference, as in Prolog
  • event-driven: handlers that run in response to events
  • handler: code that runs when a particular event occurs
  • compiled: translated to machine code before running
  • interpreted: executed by another program as it runs

Practice: practice questions for 0002.g

  1. MWC1, Turtle: first programs, sequence, errors and bugs. ↩

  2. MWC1, Names: variables, values, and functions with arguments. ↩

  3. MWC1, Dice: classes, instances, randomness, and repeated runs. ↩

  4. MWC1, Scatter: top-down and bottom-up problem solving, conditionals, return values, minimums and maximums, and plots. ↩

  5. MWC1, Pipes: programs as transformations of data, and combining commands. ↩

  6. LAI 516, Media: Scratch: constructionist design, low floor, events, and blocks. ↩