CS374: History of Programming Languages

Bill Mongan

Evolution of Language

Programming Languages Through the Years

Machine Language

  • Look up the opcode and manipulate registers directly (add, subtract, etc.)
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

Ada Lovelace

  • Programs Babbage’s Analytical Engine
  • 1840: Babbage’s Analytical Engine design

Diagram for the computation of Bernoulli numbers

Zuse Plankalkul

  • Conceptual High-Level Language in the 1940’s
  • Local boolean variables (just 1 bit!)
    • User-defined types and arrays allowed
  • Pass-by-value
  • Conditionals and iteration

Imperative Languages

  • One step at a time
  • Manipulate a variable
  • Procedures are compositions of these imperative instructions
LET x = x + 1
LET y = x * 2

Fortran

  • 1957: Formula Translator

FortranCardPROJ039.agr

Fortran

  • 3-Way IF statements (0, negative, positive)
  • DO Loops
  • Subroutines
  • Semantics: Variables beginning with the letters I through N assumed to be integers

COBOL

  • 1959: Common Business Oriented Language
  • DoD CODSYL committee effort based on Grace Hopper’s FLOW-MATIC design
  • Data records (similar to a linked list)
  • Code block demarcation i.e. END-IF
  • GO TO staetments

BASIC

AtariBasic

C

  • 1972: Dennis Ritchie
  • long and unsigned data types
  • static variable scope
  • Weak, unenforced types define data “shape” but not contents
  • Pointers

Python

  • 1991: Interpreted Language
  • Extensive Library System
  • Return of indented code blocks
  • Dynamic typing and Garbage Collection

Declarative Languages

  • Statements describe the outcome, not the implementation
  • Concise, easy to write, but complex instructions can be more difficult to read
arr = np.array([0, 1, 2, 3, 4, 5])
evens = np.where(arr % 2 > 0, arr*2, arr)

Declartaive Paradigms: Logic Programming

prereq(cs173, cs174).
prereq(cs174, cs374).
?- prereq(cs173, cs174).
Yes

Declarative Paradigms: Functional Programming

  • 1970’s: Scheme - Descendent of List Processor (LISP, 1958)
  • Composition of local functions that operate on tuples with minimal side effects

Functional Programming: Scheme

(define square
  (lambda(n)
    (* n n)))
      
(square 5)

Object-Oriented Languages

  • Generally imperative with declarative infusion
  • Oriented around manipulating data structures
  • Encapsulation, Polymorphism, Inheritance

Smalltalk

Ada

C++

  • 1985: Descendant of C
  • Combine the low-level nearness to the machine of C with high level object abstractions
  • References as an abstraction of pointers
  • Significant standard template library

Java

Today

  • A blend of popular ideas

Programming Paradigms

Every program you write reflects a hidden assumption about what a program fundamentally is. Programming paradigms are the architectural styles of software.

Programming Paradigms (cont.)

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.

Programming Paradigms (cont.)

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.

Learning Goals

By the end of this activity, you will be able to:

  • Define the four major programming paradigms (imperative, object-oriented, functional, logic) and identify the core computational model of each
  • Implement the same algorithm in at least two distinct paradigms and compare the resulting code for readability and expressiveness

Learning Goals (cont.)

  • Explain the concept of immutability and referential transparency as used in functional programming
  • Identify which paradigm(s) a given language or code fragment primarily employs and justify that identification
  • Analyze how a multi-paradigm language blends features from multiple paradigms and describe the trade-offs involved

What Is a Paradigm?

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

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.

Directions and Group Roles

Work in your POGIL team with rotated roles (Manager, Recorder, Presenter, Reflector). Consider each model and question individually first, then discuss with your group.

Directions and Group Roles (cont.)

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.

Part I: The Four Worldviews

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.

1. Imperative and Object-Oriented

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 Programming

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 Programming

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.

2. Functional and Logic

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 Programming

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).

Functional Programming (cont.)

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 Programming

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.

Model 1: Same Problem, Four Ways

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.

Model 1: Same Problem, Four Ways (cont.)

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:

Model 1: Imperative

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}")

Model 1: Object-Oriented

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))

Model 1: Pure Functions

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.

Model 1: Functional

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}")

Model 1: Logic-Style

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)})")

Critical Thinking Questions

  1. Identify the mutable state in each version (there may be none). Which versions could safely run on two halves of the string in parallel and add the results, and why?
  2. The OO version wraps the same logic in a class with lazy computation. Name one situation where that wrapping pays for itself, and one where it is ceremony without benefit.

Critical Thinking Questions (cont.)

  1. In the logic-style version, where is the loop? What does its absence tell you about who owns control flow in a declarative style?
  2. Your team’s language project must choose: mutable variables or immutable bindings (or both). List one implementation consequence of each choice for the interpreter you will build.

Part II: Paradigms in the Wild

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, and lambda. When you evaluate a language, look for its defaults and its limits, not just a single label.

3. Multi-Paradigm Reality

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.

3. Multi-Paradigm Reality (cont.)

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.

3. Multi-Paradigm Reality (cont.)

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?

Quick Check

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:

  • The imperative paradigm
  • The object-oriented paradigm
  • ✓ The functional paradigm
  • The logic paradigm

Quick Check (cont.)

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:

  • Dynamic typing
  • Object orientation
  • ✓ First-class functions
  • Static scoping

Model 2: Classify the Snippets

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.

Model 2: Paradigm Signals

Snippet Paradigm signal
account.deposit(50) ?
x := x + 1 ?
(reduce + 0 prices) ?
sibling(X,Y) :- parent(P,X), parent(P,Y). ?

Model 2: Paradigm Detective

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

Model 2: Critical Thinking Questions

  1. Classify each snippet and name the single feature that gave it away.
  2. 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.

Model 2: Critical Thinking Questions (cont.)

  1. Modern Python lets you write all four rows’ ideas (the fourth via libraries). Does multi-paradigm flexibility help or hurt the reader of a program? Connect to the Evaluating Languages activity’s topic, language evaluation criteria.

Model 3: Paradigm Costs and Benefits

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.

Model 3: Paradigm Costs and Benefits (cont.)

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:

Model 3: 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)")

Model 3: Critical Thinking Questions

  1. The functional version uses a generator expression. What does “lazy evaluation” mean in this context, and why does it save memory?
  2. For a list of 100,000 elements, which approach do you expect to be faster? Run it and report your findings.
  3. If you wanted to parallelize this computation across 4 CPU cores, which style (imperative or functional) is easier to split safely? Why?

Part III: Synthesis and Practice

4. Exercises

  1. Paradigm translation. Take the imperative vowel counter and rewrite it functionally in Python using filter and len with no assignment statements. Verify both produce identical results on three inputs.
  2. Blend audit. Pick one language a teammate knows well and list which paradigm supplies its defaults and which paradigms are available on request, with one feature as evidence each.

4. Exercises (cont.)

  1. Design straw poll. As a team, record a provisional decision for your future language: primary paradigm, mutability default, and whether functions are first-class. You may change it later; the exercise is having reasons now.
  2. Referential transparency test. Write a Python function that violates referential transparency (its output depends on something other than its arguments). Then write a pure version. Explain what changed and why the pure version is easier to test.

4. Exercises (cont.)

  1. Paradigm mashup. Write a Python program that uses all four paradigm styles to solve the same problem (filter even numbers, square them, sum). Use: imperative loop, class with method, filter/map/reduce, and a set comprehension. Show all four produce the same answer.

Reflection Prompt

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?

5. Further Reading

  • Douglas Thain. Introduction to Compilers and Language Design, Chapter 1.
  • Peter Van Roy. “Programming Paradigms for Dummies: What Every Programmer Should Know” (2009, online). A famous map of the paradigm space.

5. Further Reading (cont.)

  • Shriram Krishnamurthi. PLAI, early chapters on the functional core.
  • Rich Hickey. “Simple Made Easy” (Strange Loop 2011, YouTube). A functional programming designer’s case for immutability.

Up next: the Evaluating Languages activity turns these worldviews into criteria you can argue with — and the Warmup assignment puts them into practice.