
| Instruction Type | Opcode (6 bits) | Data (26 bits) |
|---|---|---|
| R-Type | xxxxxx | rs (5), rt (5), rd (5), shift (5), function (6) |
| I-Type | xxxxxx | rs (5), rt (5), immediate (16) |
| J-Type | xxxxxx | 26-bit address |
LET x = x + 1
LET y = x * 2
GO TO staetmentsON ERROR GOTO...arr = np.array([0, 1, 2, 3, 4, 5])
evens = np.where(arr % 2 > 0, arr*2, arr)
prereq(cs173, cs174).
prereq(cs174, cs374).
?- prereq(cs173, cs174).
Yes
(define square
(lambda(n)
(* n n)))
(square 5)
= to :=2 squaredEvery program you write reflects a hidden assumption about what a program fundamentally is. Programming paradigms are the architectural styles of software.
Just as the same building function — say, a library — can be expressed as a Gothic cathedral, a glass-and-steel Modernist cube, or a terracotta Art Deco tower, the same computation can be expressed as a sequence of machine commands, a conversation among objects, a composition of mathematical functions, or a declarative set of facts and rules.
Each style carries different trade-offs in clarity, scalability, and safety, and the language you design this semester will implicitly commit to one or more of them. Picking up the map from Welcome: Why Study Programming Languages?, today we tour those styles systematically.
By the end of this activity, you will be able to:
A paradigm is a worldview about what a program is: a sequence of commands, a society of objects, a composition of functions, or a set of facts and rules. Today we tour the four major paradigms with the same small problem expressed in each, because your team’s language will have to pick a side (or blend several, as most modern languages do). The arc: imperative → object-oriented → functional → logic → multi-paradigm reality.
Before You Begin: This activity assumes you can:
- Write and trace basic Python programs with variables, loops, and conditionals
- Define and call functions in Python, including passing functions as arguments
- Explain what a class and object are at a high level (instance variables, methods)
If any of these feel shaky, review them first before working through the models.
Work in your POGIL team with rotated roles (Manager, Recorder, Presenter, Reflector). Consider each model and question individually first, then discuss with your group.
The Recorder posts answers to the Class Activity Questions discussion board; the Presenter reports out areas of disagreement or alternative approaches. After class, respond to the reflective prompt individually in your notebook.
Watch out! Paradigms are not mutually exclusive categories. A single language can — and most modern languages do — support multiple paradigms simultaneously. When we label a language “object-oriented,” we mean that OO is its dominant style, not that you cannot write imperative loops in it.
Think of imperative programming like a step-by-step recipe: “crack two eggs, whisk them, pour into pan, stir until cooked.” You specify every action in order, and the state of the dish changes with each step. Object-oriented programming is more like a kitchen brigade — each station (the sauté cook, the pastry chef) owns its own tools and ingredients, responds to requests from the head chef, and hides the messy details of how it does its work.
Imperative: a program is a sequence of state changes. The core concepts are variables (named mutable cells), assignment, and control flow (sequencing, selection, iteration). The model matches the machine: memory cells change over time. C is the archetype; most languages contain an imperative heart.
Object-oriented: a program is a society of objects exchanging messages. State is encapsulated inside objects; behavior travels with the data it governs; polymorphism lets the same message mean different things to different receivers. OO answers the imperative paradigm’s scaling problem: when everything can mutate everything, large programs become unpredictable, so OO draws fences.
Functional programming treats a program the way a mathematician treats a formula: f(g(x)) has one answer for a given x, no side effects, and no hidden state. Logic programming goes even further — it is like asking a very smart search engine a question (“who are all the ancestors of Tom?”) and letting the engine figure out how to find the answer from the facts you gave it; you never write a loop at all.
Functional: a program is the composition of functions. The central commitments are immutability (values do not change; new values are produced), first-class functions (functions are values that can be passed and returned), and referential transparency (an expression can be replaced by its value without changing behavior).
Where imperative code says do this, then that, functional code says the answer is this transformation of that. Scheme, Haskell, and increasingly the cores of Python and Java.
Logic: a program is facts and rules; the runtime searches for proofs. In Prolog one declares parent(tom, mary). and rules like ancestor(X, Y) :- parent(X, Y)., then asks queries; the how belongs entirely to the engine. Logic programming is the purest case of declarative programming, stating what is true rather than what to do.
The best way to feel the difference between paradigms is to solve exactly the same problem four ways and notice what each version forces you to think about.
As you read each block below, ask yourself: what is mutable here? Who controls the loop? Could I run this twice on different inputs simultaneously without the two runs interfering?
Count the vowels in a string. Run each approach and compare:
Imperative — explicit state mutation:
s = "programming languages are fascinating"
count = 0
for ch in s:
if ch in "aeiou":
count = count + 1
print(f"Imperative count: {count}")
Object-oriented — encapsulated state:
s = "programming languages are fascinating"
class VowelCounter:
VOWELS = set("aeiou")
def __init__(self, text):
self.text = text
self._count = None # lazy computation
def count(self):
if self._count is None:
self._count = sum(1 for ch in self.text if ch in self.VOWELS)
return self._count
def __repr__(self):
return f"VowelCounter({self.text!r}, count={self.count()})"
vc = VowelCounter(s)
print(f"OO count: {vc.count()}")
print(repr(vc))
Watch out! “Pure function” and “function” are not the same thing. A pure function’s output depends only on its arguments and it causes no side effects (no printing, no file writes, no mutation of shared variables). Python’s
print()is a function, but it is not pure — it changes the state of the terminal. Keep this distinction sharp as you read the functional version below.
Functional — composition of pure functions:
from functools import reduce
s = "programming languages are fascinating"
# No variables mutated; each step is a pure function
is_vowel = lambda ch: ch in "aeiou"
to_one = lambda _: 1
add = lambda a, b: a + b
count = reduce(add, map(to_one, filter(is_vowel, s)), 0)
print(f"Functional count: {count}")
# Even more compact with sum + generator:
count2 = sum(1 for ch in s if ch in "aeiou")
print(f"Comprehension count: {count2}")
Logic-style — simulate Prolog with constraint search:
# Python simulation of logic-style declarative counting
s = "programming languages are fascinating"
# "Facts" — vowel membership
vowels = {"a", "e", "i", "o", "u"}
# "Rule" — count is cardinality of {ch | ch in s AND vowel(ch)}
count = len({i: ch for i, ch in enumerate(s) if ch in vowels})
print(f"Logic-style count: {count}")
# More Prolog-like: unification via list comprehension
answer = [ch for ch in s if ch in vowels]
print(f"Witness list: {answer[:10]}... (length {len(answer)})")
Watch out! Saying “Python is object-oriented” is like saying “New York is a financial city” — true but incomplete. Python has objects everywhere, and supports imperative style, and ships functional tools like
map,filter, andlambda. When you evaluate a language, look for its defaults and its limits, not just a single label.
In practice, the clean four-way taxonomy you just explored is a teaching simplification. Real languages are hybrids shaped by history, performance constraints, and the preferences of their designers. As you read this section, think of the paradigm spectrum as a dial rather than four locked boxes — the interesting design question is where a language sets its dial by default, and how far the user can turn it.
Pure paradigm languages are rare; blends are the norm. Python is imperative and OO with a functional toolkit (map, filter, comprehensions, lambda). Java added lambdas and streams in 2014.
JavaScript mixes prototypal objects with pervasive higher-order functions; Rust is imperative with functional pattern matching and an ownership discipline. The paradigm question for a designer is not which one but which defaults: what does the language make easy, and what does it make possible?
A language guarantees that no value can ever be modified after creation and that functions may be stored in variables and passed as arguments. These two guarantees are the signatures of:
A program in language X cannot run a function until every argument is known. Language Y can pass a function as a value and call it later. The property that distinguishes Y from X is:
Each paradigm leaves fingerprints in the code. An assignment like x := x + 1 screams “named mutable cell” — that’s an imperative tell. A colon-dash rule like ancestor(X,Y) :- parent(X,Y). has no variables at all in the traditional sense — that’s logic. Learning to spot these signals in unfamiliar code is the skill that lets you read any language quickly, even before you know its syntax.
| Snippet | Paradigm signal |
|---|---|
account.deposit(50) |
? |
x := x + 1 |
? |
(reduce + 0 prices) |
? |
sibling(X,Y) :- parent(P,X), parent(P,Y). |
? |
Paradigm Detective — run this and read the clues:
snippets = [
("account.deposit(50)", "sends a message to an object"),
("x := x + 1", "named cell changes over time"),
("(reduce + 0 prices)", "function applied to function applied to list"),
("sibling(X,Y) :- parent(P,X), parent(P,Y)", "rule: X and Y share a parent"),
]
paradigm_hints = {
"sends a message to an object": "Object-Oriented",
"named cell changes over time": "Imperative",
"function applied to function applied": "Functional",
"rule: X and Y share a parent": "Logic/Declarative",
}
for snippet, hint in snippets:
for key, paradigm in paradigm_hints.items():
if key in hint:
print(f" [{paradigm:20}] {snippet}")
break
account.deposit(50) mutates state and sends a message to an object. Is OO a kind of imperative programming with better manners, or something fundamentally different? Take a team position.Paradigm choice is not just an aesthetic preference — it has measurable consequences for memory use, parallelizability, and readability. The functional generator in this model never materializes a full intermediate list, while the imperative version builds one in memory before summing it.
This difference seems trivial at 100 items but matters enormously at 100 million. Keep an eye on the memory numbers printed at the end.
The choice of paradigm shapes what is easy and what is hard. Run this performance comparison:
import time
data = list(range(1, 100001))
target = lambda x: x % 2 == 0
# Imperative
t0 = time.perf_counter()
result_imp = []
for x in data:
if target(x):
result_imp.append(x * x)
total_imp = sum(result_imp)
t1 = time.perf_counter()
# Functional (generator — lazy, low memory)
t2 = time.perf_counter()
total_func = sum(x*x for x in data if target(x))
t3 = time.perf_counter()
print(f"Imperative: {total_imp} ({(t1-t0)*1000:.2f} ms)")
print(f"Functional: {total_func} ({(t3-t2)*1000:.2f} ms)")
print(f"Same result? {total_imp == total_func}")
# Key observation: functional version never builds an intermediate list
import sys
imp_list_size = sys.getsizeof(result_imp)
print(f"Imperative list size in memory: {imp_list_size} bytes")
print(f"Functional generator: no intermediate list (lazy evaluation)")
filter and len with no assignment statements. Verify both produce identical results on three inputs.filter/map/reduce, and a set comprehension. Show all four produce the same answer.In your notebook: which paradigm fits the way you naturally think about problems, and which feels most foreign? Describe one problem from another course (mathematics, biology, economics) and which paradigm would express it most directly. Then: now that you have seen the cost/benefit tradeoffs, does your answer change when the problem needs to scale to millions of items?
Up next: the Evaluating Languages activity turns these worldviews into criteria you can argue with — and the Warmup assignment puts them into practice.