Programming Language Theory

Programming Paradigms

Python, JavaScript, Go, Rust, Haskell - these are not different tools. They are different answers to the same question: how is complexity managed? A paradigm is not syntax. It is a model of computation. Rust's ownership system is linear types from a 1970s theoretical paper. Haskell's monads are category theory from 1958. React's immutable state is the same idea John McCarthy put into Lisp in 1958. Everything has already been invented - it just gets renamed and repackaged. Understanding five paradigms means a new language stops being surprising, and starts being recognizable as a rediscovery of an old idea.

  • **Rust** = linear types (1970s) + affine type theory. The borrow checker is not a new invention - it is a formal system from academic papers, finally going mainstream. Zero-cost abstractions handle packets at Cloudflare at 1M RPS
  • **React/Redux** is the functional paradigm: immutable state + pure reducers. Ideas from Haskell and ML, packaged in JavaScript. Used in products with 100M+ users
  • **Apache Spark** uses the functional model (map/flatMap/reduce) to process petabytes of data. The same task in the imperative style would be a synchronization nightmare
  • **SQL** is Codd's relational calculus from 1970 plus the logic paradigm of Prolog. Every SELECT is a logical query; the optimizer decides "how" instead of the programmer

Imperative Programming: Step-by-Step Instructions

Ada Lovelace wrote the first program in 1843 for Babbage's Analytical Engine. No abstractions - just a list of arithmetic operations in sequence. That is **imperative programming**: the computer receives a **sequence of commands**, each one mutating memory. In 180 years the syntax has changed. The computational model has not.

**The imperative paradigm** is a programming style in which a program is a **sequence of commands** that mutate state. Key elements: **variables** (memory cells), **assignment** (state change), **loops** (repetition), **branching** (conditionals). A program is a recipe: step 1, step 2, step 3...

AdvantagesDisadvantages
Approachable for beginners - resembles everyday instructionsMutation bugs: who changed the variable, and when?
Full control over every stepHard to parallelize - steps depend on each other
Memory-efficient - mutate in placeCode bloat: 10 lines of logic → 50 lines of control flow
Close to the metal - the CPU is inherently imperativeHard to formally prove correctness

From Ada Lovelace to Go

The first program in history was written by Ada Lovelace in 1843 - for Charles Babbage's Analytical Engine. It was purely imperative: a sequence of arithmetic operations to compute Bernoulli numbers. The lineage has never broken: assembly (1950s), FORTRAN and COBOL (1957), C (1972), Go (2009). Even "modern" languages like Go deliberately embrace imperative simplicity.

**Mutation is the primary source of bugs.** When 5 functions all modify the same variable, tracking down who corrupted the data becomes a detective story. That is precisely why other paradigms emerged - they each try to solve the problem of uncontrolled state mutation.

What key trait distinguishes imperative programming from other paradigms?

Declarative Programming: What, Not How

Edgar Codd invented relational algebra in 1970 - and with it an idea that keeps getting reinvented: describe WHAT is needed, not HOW to find it. SQL is the direct descendant. React applies it to UI. Terraform applies it to infrastructure. The **declarative** approach works because it offloads optimization: a SQL query planner explores 50 execution strategies; a developer would explore 2.

**The declarative paradigm** is a style in which the programmer describes the **desired result** and the system figures out how to achieve it. The PROPERTIES of the solution are described, not the steps. SQL, HTML, CSS, regular expressions, React, Terraform - these are all declarative tools.

  • Imperative DOM (JavaScript) — const div = document.createElement('div'); div.className = 'card'; const h2 = document.createElement('h2'); h2.textContent = user.name; div.appendChild(h2); container.appendChild(div); // 6 lines of manual DOM manipulation
  • Declarative UI (React) — <div className="card"> <h2>{user.name}</h2> </div> // Describe WHAT to show; React updates the DOM itself

**Regex** is yet another declarative tool. The expression `^[A-Z][a-z]+$` describes a pattern: "starts with an uppercase letter, followed by lowercase." No loop checking each character is written - the regex engine figures out how to search. HTML describes the **structure** of a page. CSS describes its **appearance**. Terraform describes **infrastructure**. Everywhere the same idea: description, not instruction.

**Declarative code is easier to read and maintain** because it is closer to the problem statement. An SQL query reads almost like an English sentence: "SELECT name, salary FROM employees WHERE department = Engineering". The imperative equivalent requires reading every step just to understand the intent.

ToolWhat it describesWho decides "how"
SQLWhich data is neededDBMS optimizer
HTMLDocument structureBrowser engine
CSSElement appearanceLayout engine
React JSXHow the UI should lookVirtual DOM reconciler
RegexString patternRegex engine (NFA/DFA)
TerraformDesired infrastructure stateTerraform provider

Which of these tools is NOT declarative?

Functional Programming: Functions as LEGO Bricks

John McCarthy built Lisp in 1958 on top of Church's lambda calculus. The core idea: a function is a mathematical object, not a procedure. f(x) = x² **always** returns the same result for the same x. No dependency on external world. No side effects. These are called **pure functions** - and the entire functional paradigm follows from this single constraint, which turns out to deliver enormous payoffs for parallelism and testability.

**Functional programming (FP)** rests on three pillars: 1. **Pure functions** - the result depends only on the arguments; no side effects. 2. **Immutability** - data is never mutated; new copies are created. 3. **Functions as first-class citizens** - functions can be passed as arguments, returned from functions, and stored in variables.

**Why does FP simplify testing?** Testing a pure function is straightforward: pass an input → check the output. No mocks, stubs, or environment setup needed. And **immutability** eliminates an entire class of bugs: if data never changes, race conditions on concurrent access become impossible.

**map, filter, reduce** - the three musketeers of FP. `map` transforms each element. `filter` keeps the matching ones. `reduce` folds a collection into a single value. Almost any loop can be rewritten as a combination of these three functions.

Functional programming means abandoning loops in favor of cryptic map and reduce

FP is a way of thinking: break a problem into small pure functions and combine them like LEGO bricks. map/filter/reduce are tools, not ends in themselves. The core value is predictability and testability.

A pure function is a contract: only the arguments affect the result. This makes code testable without mocks and parallelizable without mutexes. Apache Spark, JAX, and React all apply this idea at industrial scale. The for loop is not forbidden - but every loop with mutation is a potential race condition in a concurrent system.

The function readFile(path) reads a file from disk and returns its contents. Is it pure?

OOP: Objects as Small Worlds

Alan Kay built Smalltalk in 1972 with biology as the inspiration: cells encapsulate state and communicate through chemical signals. In his model, an object is not a data container - it is an **autonomous agent** that receives messages and decides how to respond. Data and behavior are inseparable. C++ and Java later reinterpreted this as class hierarchies, and Kay spent decades clarifying that this was not what he meant.

**OOP** combines data and behavior into **objects**. Four principles: 1. **Encapsulation** - data is hidden, accessed through methods. 2. **Inheritance** - a new class inherits the properties of an existing one. 3. **Polymorphism** - one interface, different implementations. 4. **Abstraction** - highlighting the essential, hiding the details.

Alan Kay and "Message Passing"

Alan Kay, creator of Smalltalk (1972), coined the term "object-oriented programming". But he later expressed regret: "I invented the term OOP, and I can tell you I did not have C++ in mind." For Kay, the key was not inheritance but MESSAGE PASSING between objects - like cells in an organism communicating through chemical signals. An object doesn't call a method - it sends a message, and the recipient decides how to respond.

**Inheritance is not the core idea of OOP.** Many people equate OOP with deep class hierarchies. In practice, experienced developers use inheritance sparingly. The "Composition over Inheritance" principle (Gang of Four, 1994) says: assemble behavior from components, don't inherit from ancestors.

What did Alan Kay consider the core idea of OOP?

Logic Programming: Facts, Rules, and Inference

Alain Colmerauer built Prolog in 1972 on first-order logic. The radical idea: a program is not an algorithm - it is a **knowledge base**. Facts plus rules. Submit a query, and the system constructs a proof through unification and backtracking. No "how to search" - only "what is true". The same model as SQL, but with recursion, inference, and automatic proof generation built in.

**Logic programming** is grounded in formal logic. A program is a set of **facts** (assertions about the world) and **rules** (logical relationships). **Prolog** (1972) is the primary logic language. The programmer describes WHAT IS TRUE, and the system uses **unification** and **backtracking** to find answers.

**SQL is logic programming too!** `SELECT * FROM users WHERE age > 18 AND city = 'Moscow'` is a logical query: "find all records for which it is TRUE that age > 18 AND city = Moscow". Datalog is a database query language based on Prolog. Many developers use logic programming every day through SQL without realizing it.

FeaturePrologSQLDatalog
TypeGeneral-purposeDB queriesDB queries
Factsparent(tom, bob).INSERT INTO ...parent(tom, bob).
Rulesancestor(X,Y) :- ...VIEW / subqueryancestor(X,Y) :- ...
Query?- ancestor(tom, X).SELECT ... WHERE ...?- ancestor(tom, X).
SearchBacktrackingOptimizerFixed point
Use casesAI, expert systemsAll RDBMSProgram analysis

Logic programming is an 80s curiosity not used in real-world projects

SQL is the world's most popular logic language. Constraint solvers power Google OR-Tools, SAT solvers for chip verification, and Datalog for code analysis (the Souffle tool at Facebook). The logic paradigm is alive and thriving - just not always under the name Prolog.

Datalog is the logical language powering Datomic (Rich Hickey's database) and static code analysis via Semmle/CodeQL, acquired by GitHub. Every CodeQL query is a logical inference over a code AST. Millions of GitHub repositories are checked through the logic paradigm daily. Prolog is niche; the idea is not.

In a Prolog program with facts parent(tom, bob) and parent(bob, alice), and the rule ancestor(X,Y) :- parent(X,Z), ancestor(Z,Y). What does the query ?- ancestor(tom, alice) return?

Key Ideas

  • **A paradigm is a model of computation, not syntax.** Python can be imperative, functional, and OOP - in the same file
  • **Imperative**: steps + mutation. Clear to the machine, dangerous for humans - mutation causes 70% of bugs in concurrent code
  • **Declarative + Logic**: describe WHAT, the system figures out HOW. A SQL optimizer tries 50 execution plans; a programmer would try 2
  • **Functional**: pure functions are a test engineer's dream. No side effects = no ordering dependency = free parallelism
  • **OOP**: Kay's real idea was message passing, not hierarchies. Gang of Four said "prefer composition over inheritance" in 1994 - 30 years later it's still ignored
  • **Rust ownership, Haskell monads, React reducers** - all are old academic ideas with new names. Understanding paradigms means reading between the lines of any new language

Related Topics

Programming paradigms are the entry point into programming language theory:

  • Type Systems — Each paradigm uses types differently: OOP uses nominal typing, FP uses algebraic data types, logic programming uses terms.
  • Lambda Calculus — The mathematical foundation of functional programming: any computable function can be expressed via λ-abstraction.
  • Compilers — A compiler translates declarative or functional code into imperative machine instructions - a bridge between paradigms.

Вопросы для размышления

  • Consider a recent project. Which paradigms were used? Most likely at least two - which ones, and why?
  • In designing a GPS navigator, which paradigm fits each part best: route finding, map rendering, voice command processing?
  • Why are most modern languages (Python, Kotlin, Rust, Swift) multi-paradigm? Can a "pure" paradigm cover all the needs of a real-world project?

Связанные уроки

  • comp-01-intro — Understanding paradigms helps see how compilers handle different coding styles
  • plt-02-type-systems — Type systems are built on top of paradigms: functional → type inference, OO → polymorphism
  • fl-01-intro — Formal languages explain the mathematical foundations of logic and functional paradigms
  • dm-01 — Logic programming (Prolog) is executable discrete mathematics
  • comp-04-interpreter-vs-compiler
Programming Paradigms

0

1

Sign In