Last updated: 2026-09-23

U
Undergraduate level

Polyglot Programming & Paradigm Shifting

Languages are ephemeral; paradigms are foundational. Learn only syntax and every new language is a fresh start; learn how memory, execution flow and state change are managed across paradigms and you can pick up a new language in a weekend — because you will recognise it as a new accent, not a new tongue.

The Meta-Skill of Language Translation

When you meet an unfamiliar language, interrogate it with the same six questions and map the answers onto what you already know. This approach — organising paradigms by the underlying dimensions they vary along (state, control flow, binding, and so on) rather than treating each language's syntax as sui generis — is the same one Van Roy and Haridi take at book length, building up every major paradigm from a shared kernel language by adding one concept at a time1: paradigms turn out to be points in a shared space, not an unrelated list to memorise separately.

QuestionWhat you're mappingExample spread
Where do values live, and who frees them?Memory modelGC heap (Java/Ruby) ↔ ownership (Rust) ↔ manual (C)
When is a name visible, and when does its value die?Scope & lifetimeLexical closures ↔ block scope ↔ dynamic scope
When are types checked, and how strong is the promise?Type disciplineStatic/nominal (Java) ↔ static/structural (Go, TS) ↔ dynamic/duck (Ruby, Python)
How does one piece of behaviour substitute for another?PolymorphismSubtyping ↔ interfaces/protocols ↔ higher-order functions ↔ generics
What changes, and who is allowed to change it?State modelMutable objects ↔ immutable values ↔ monadic effects
Who decides what runs next?Control flowCall stack ↔ event loop ↔ solver/backtracking

The Paradigm Landscape

  • Imperative & procedural. Step-by-step state manipulation through explicit instructions grouped into routines. The focus is how: sequence, selection, iteration. Every mainstream language contains this core, which is why it is taught first — and why its habits (shared mutable state everywhere) are what the other paradigms exist to discipline.
  • Object-oriented. Encapsulating state and behaviour into cohesive objects that communicate — as the rest of this track explores — by message passing, with polymorphism and interface contracts enforcing structural invariants. The deep idea is less "classes" than protected state behind a behavioural boundary (see Event-Driven Programming for messaging taken to its logical conclusion) — classes are simply the dominant mechanism for building that boundary, not the only one; see Prototype-Based Programming for the delegation-based alternative used by Self and JavaScript.
  • Functional. Computation as evaluation of pure functions; mutable state and side effects pushed to the edges. Higher-order functions are first-class citizens: functions consume and return functions. You already use this paradigm every time you write a map/filter chain, a Ruby block, or a Java stream — the paradigm arrived inside the OO languages while nobody was watching.
  • Event-driven. Control flow inverted: the system idles until a message arrives — a click, a packet, a domain event — and routes it to a handler. Covered in depth on Event-Driven Programming, including what the inversion costs in traceability.
  • Logic programming. Declare facts and rules about what's true, and let a resolution engine derive new facts by backward-chaining through them — Prolog's approach, formalised by Kowalski's insight that a restricted fragment of first-order logic (Horn clauses) could double as an executable programming language2. You're not writing a search; you're writing the space of facts the engine searches.
  • Goal-oriented programming. A close cousin, easy to conflate with logic programming but answering a genuinely different question: not "is this true?" but "what sequence of actions gets me from here to a declared goal state?" Each action declares its own preconditions and effects, and a planner searches for an ordered, often cost-minimising, path from the current state to the goal — the STRIPS formalism from 1970s AI planning research3, repackaged for real-time NPC behaviour as GOAP and demonstrated as a runnable planner (alongside Prolog-style backward chaining) in PatLang's goal-oriented programming demo. A build tool like Make is the same idea wearing yet another disguise — declare a goal (a target file), declare what it depends on, and let the tool work backward from the goal to find which rules actually need to run; see below. See GOAP vs Rule-Based Reasoning for the same small domain solved both ways side by side, and for real problems each approach actually suits.

One Problem, Six Accents

Total the prices of in-stock items" — watch the state and control flow move between paradigms:

# Imperative: explicit accumulation, mutable loop state
total = 0
for item in items:
    if item.in_stock:
        total += item.price
# Object-oriented: the collection owns the behaviour
total = basket.total_in_stock()   # state hidden behind the message
# Functional: expression over immutable data, no assignment
total = sum(i.price for i in items if i.in_stock)
# Event-driven: totals maintained by reacting to facts
bus.subscribe("item.stocked",   lambda e: ledger.add(e.price))
bus.subscribe("item.sold_out",  lambda e: ledger.remove(e.price))
% Logic: declare the relation; the engine finds the total
in_stock_price(P) :- item(I), in_stock(I), price(I, P).
total(T) :- findall(P, in_stock_price(P), Ps), sum_list(Ps, T).
# Goal-oriented (Make): declare the goal (an up-to-date total.txt) and
# what it depends on; make works backward from the goal to find which
# rules must fire, and in what order — you never write "run step 1, then
# step 2", only what each step needs and produces.
total.txt: in_stock_items.txt
	awk '{s+=$$2} END {print s}' in_stock_items.txt > total.txt

in_stock_items.txt: items.txt
	awk '$$3=="instock" {print $$1, $$2}' items.txt > in_stock_items.txt

Run make total.txt and Make doesn't execute a script from top to bottom — it starts from the goal, notices total.txt depends on in_stock_items.txt, notices that depends on items.txt, and only then decides which rules actually need to run and in what order. That's the same backward-from-the-goal shape as a GOAP planner searching from a desired world-state back to the current one, just applied to files and timestamps instead of a game character's preconditions and effects.

Choosing (and Composing) the Tool

Real architectures rarely stay inside one paradigm; they compose them. A typical modern service is an object-oriented shell (modules, dependency boundaries, SOLID discipline) around functional stream processing (pure transformations over immutable events), driven by an event loop, with a declarative configuration layer on top. The skill is not picking a winner — it is noticing which paradigm's guarantees each layer of the problem needs: auditability wants immutable events; complex domain invariants want encapsulating objects; concurrency wants purity; interaction wants events.

The practical advice: learn one language from an unfamiliar paradigm properly — far enough to feel its idioms stop being weird. Each paradigm permanently adds a lens, and code in your home language improves because you now see which of its features are borrowed lenses too.

References


  1. Van Roy, P., & Haridi, S. (2004). Concepts, Techniques, and Models of Computer Programming. MIT Press.

  2. Kowalski, R. (1974). Predicate logic as a programming language. Proceedings of IFIP Congress 1974, Stockholm. North-Holland, 569–574.

  3. Fikes, R. E., & Nilsson, N. J. (1971). STRIPS: A new approach to the application of theorem proving to problem solving. Artificial Intelligence, 2(3–4), 189–208. https://doi.org/10.1016/0004-3702(71)90010-5