2. Context and Background
In this chapter, we establish a practical context and provide some background information for the study of programming languages.
Note
Key terms used in this chapter:
Functional requirement (FR): a specification of what the system must do — the mapping from inputs to outputs or observable behaviours.
Nonfunctional requirement (NFR): a quality attribute of the system itself, such as testability, performance, reliability, or maintainability.
Refactoring: improving the internal structure of code without changing its external behaviour. Refactoring improves NFRs such as maintainability and testability.
Continuous integration (CI): the practice of automatically building and testing the system each time code is committed, catching integration errors early.
Programming paradigm: a fundamental style of programming that reflects a model of computation. Different paradigms provide different abstractions; see the Programming language history and paradigms section below.
2.1. Software requirements
In most cases, we develop software to provide some form of value:
learn a language, library, framework, platform, technique, or tool (see also the ThoughtWorks Technology Radar)
solve a problem
produce an asset
There is usually some tension among these three activities.
The basic categories of requirements are
functional (FR)
output as function of input: y = f(x)
or some other description of observable behavior
batch
interactive/event-based
nonfunctional (NFR): additional properties of f, e.g.
testability
most important nonfunctional requirement
allows testing whether functional requirements are met
good architecture often happens as a side-effect (APPP pp. 36-38), such as separating I/O from core functionality
performance
scalability
e.g. performance for large data sets: asymptotic order of complexity
(big-Oh) in terms of input size n
reliability
maintainability
static versus dynamic NFRs
Several common questions and issues related to requirements arise:
how do requirements relate to the project development lifecycle?
BUFD versus MVP
how do testing and refactoring relate to requirements?
The following figure by Kazman relates unit operations (high-level generalizations of refactorings) to software quality factors (nonfunctional requirements). Each arrow indicates that applying the unit operation (e.g., modularize, abstract) tends to improve the corresponding quality factor (e.g., maintainability, testability). Notably, modularize simultaneously improves both maintainability and testability.
Kazman et al.: unit operations and the quality factors they improve.
As we will see throughout the course, programming language features directly support or hinder the satisfaction of NFRs. Static type systems improve reliability by catching errors at compile time. Immutability and pure functions improve testability by eliminating hidden state. Built-in concurrency primitives improve scalability. The language design chapters will repeatedly refer back to these requirements as motivation for language features.
2.2. Overview of a lightweight development process
A successful development process usually comprises these minimal elements:
-
tests represent expectations of how the software should behave
when expressed as code, these are
fun to produce (like other coding)
convenient to run frequently
fix system-under-test (SUT) (not tests themselves) until tests pass
retest every time
a feature is added
the code is refactored
-
improve the quality of the code without changing its behavior
macro level: nonfunctional requirements (quality factors)
micro level: code smells
The process tree example illustrates continuous integration using various hosted services:
The click counter example includes additional hosted continuous integration and delivery targets suitable for mobile app development.
2.3. Software design principles and patterns
The software development community has identified various principles intended to guide the design and development process, for example:
The community has also developed a body of design patterns that represent reusable solutions to recurring problems. Some key design patterns we will rely on in this course include
Iterator
Strategy
Command
Composite
Decorator
Visitor
Abstract Factory
Observer
We will recap these throughout the course as needed.
Note
Language-specific design patterns are called idioms.
2.4. Programming language history and paradigms
The resources in this section cover fundamental models of computation, language paradigms, and language principles.
overview talk by Läufer and Thiruvathukal
programming languages paradigms: diagram by Van Roy
Appendix: Programming Language Design Principles by MacLennan
A programming paradigm is a fundamental style or approach to programming that reflects a mental model of computation. It determines what abstractions are available, how state and control flow are managed, and what kinds of problems a language is best suited for. Peter Van Roy’s paradigm diagram (linked above) organises over 30 paradigms by their core features.
The major paradigms covered in this course are:
Imperative (The Imperative Programming Paradigm): computation as a sequence of state-changing statements; the dominant paradigm in languages like C, Java, and Python.
Object-oriented (The Object-Oriented Programming Paradigm): computation modelled through objects that encapsulate state and behaviour; extends the imperative paradigm with encapsulation, inheritance, and polymorphism.
Functional (The Functional Programming Paradigm): computation as the evaluation of mathematical functions; emphasises immutability, higher-order functions, and algebraic data types; exemplified by Haskell, Scala, and Clojure.
Logic (The Logic Programming Paradigm): computation as logical deduction; programs are sets of facts and rules and execution is query-driven; exemplified by Prolog.
Concurrent (The Concurrent Programming Paradigm): computation as multiple simultaneous activities; raises issues of synchronisation, shared state, and nondeterminism.
A foundational theoretical result bounds all of these paradigms: the Church-Turing thesis states that every effectively computable function can be computed by a Turing machine (or equivalently, by a lambda calculus expression, or by a recursive function). A language capable of expressing every Turing-computable function is called Turing complete. Understanding this equivalence helps explain why paradigms differ in expressiveness of style but not in fundamental computational power.
2.5. Popularity indices and performance comparisons
There are a number of programming language popularity indices and performance comparisons. Before drawing any conclusions from these indices, it is important to understand their methodology.
GitHub language popularity (scroll about half-way down to the relevant section)
Note
When consulting any popularity or performance index, ask: What is the data source? (search queries, job postings, GitHub commits, or benchmark runs each capture different things.) What counts as “a language”? (Some indices conflate dialects or treat Jupyter notebooks as Python.) Does the index weight by project size or contributor count? Understanding methodology prevents over-interpreting rankings. The PYPL and TIOBE indices publish their methodology; always read it before drawing conclusions.
2.6. Summary and further reading
This chapter establishes the conceptual and practical context for the rest of the book. Software requirements — both functional and nonfunctional — motivate language and paradigm choices throughout the course: testability drives separation of concerns, performance motivates complexity analysis, and maintainability favors clean, modular designs. Lightweight development practices (automated testing, refactoring, continuous integration) and SOLID design principles and patterns recur in every paradigm chapter as the standards against which code quality is measured.
The programming language history and paradigms section shows that no single paradigm is universal: imperative, object-oriented, functional, logic, and concurrent programming each reflect a distinct model of computation, and real systems routinely combine them. Because all Turing-complete languages share the same fundamental expressive power, the interesting questions are about expressiveness of style, safety guarantees, and fit to the problem domain — themes developed in depth throughout the book.
Further reading:
Robert W. Sebesta, Concepts of Programming Languages (12th ed., Pearson) — accessible survey of language design, paradigms, and implementation.
Peter Van Roy and Seif Haridi, Concepts, Techniques, and Models of Computer Programming (MIT Press, 2004) — systematic treatment of paradigms organised around the concept diagram linked in this chapter.
Martin Fowler, Refactoring: Improving the Design of Existing Code (2nd ed., Addison-Wesley, 2018) — the standard reference on refactoring and code smells.
Robert C. Martin, Clean Code (Prentice Hall, 2008) — practical guidance on writing maintainable code aligned with the NFRs discussed here.
ThoughtWorks Technology Radar — regularly updated survey of languages, frameworks, and tools, useful for understanding current industry practice.