Hello! Welcome to your first lesson on programming theory.
This course is designed to build a strong theoretical foundation for understanding programming languages and paradigms, moving beyond the practical application of specific languages like Python. As you requested, we'll explore functional programming, typing systems, and other core concepts, frequently referencing John C. Mitchell's "Concepts in Programming Languages" to ground our discussions.
We begin with Module 1, "Foundations of Functional Programming." The first step in understanding the functional paradigm is to grasp its most fundamental building block. This lesson addresses the following learning outcome:
Distinguish between expressions and statements, using examples from early imperative languages (e.g., C) and functional languages (e.g., Lisp).
This distinction is crucial because it represents a primary philosophical divide between imperative languages, which are built from commands that change the state of the computer, and functional languages, which are modeled on mathematical functions that transform data. Understanding this will be the bedrock for everything that follows, including concepts like purity and referential transparency.
Let's get started.
1. The Core Distinction: Expressions vs. Statements
At a high level, the difference is simple:
- An expression is a piece of code that evaluates to a value. Think of
2 + 2, which evaluates to4, oruser.name, which evaluates to a string. - A statement is a piece of code that performs an action or causes a side effect. Think of an assignment like
x = 5, aforloop, or aprintcommand.
To get a more formal and historically-grounded perspective, please start by reading the definitions from John C. Mitchell's course slides.
Concepts in Programming Languages
These slides from 'Concepts in Programming Languages' by John C. Mitchell provide the formal definitions we'll use. We'll also look at how this distinction appeared in early imperative and functional languages.
Please read the following sections from the PDF: Go to slide 29, titled 'Basic Concepts', for the core definitions of 'Expression', 'Statement', and 'Declaration'. On that slide, under the heading 'Programming-language phrases – reminder', read phrase definitions. Briefly look at slides 32–35, starting with 'Part A: Imperative Languages', to see how an early imperative language, FORTRAN, was structured around statements. Beginning on slide 32, read FORTRAN overview. Finally, read slides 42–44, starting with 'LISP – LISt Processing', to see the contrasting philosophy of an early expression-based language. Beginning on slide 42 in the 'LISP: functions, recursion, and lists' topic, read LISP overview.
To deepen this understanding, let's look at a detailed explanation from a popular programming Q&A site.
language agnostic - Expression versus Statement
This Stack Overflow discussion provides an excellent, language-agnostic explanation of the distinction, its history, and how it has evolved.
Please read the top-voted answer by user Joel Spolsky. Focus on: The initial, clear definitions of expression and statement. The historical context using FORTRAN, where the distinction was very strict. How C began to blur the lines by allowing expressions to be used as statements and to have side effects. The summary of how functional languages like Lisp and ML largely eliminate the syntactic distinction.
2. The Imperative Tradition: Statement-Oriented Design
As you just read, early imperative languages like FORTRAN and ALGOL were built around statements. A program was a sequence of commands to be executed in order: do this, then do that, then change this variable. Expressions existed, but often only within statements (e.g., on the right side of an assignment).
C, which you have some familiarity with, inherits this tradition but, as noted in the Stack Overflow article, begins to blur the lines. Let's see this in practice.
C Programming Tutorial 9 - C Basics Part 1 - Variables, Expressions, Statements
This video, though introductory, provides a clear, practical demonstration of expressions and statements within the C language.
Please watch the following two segments from the 'C Programming Tutorial' by Caleb Curry: Understanding Expressions: About four and a half minutes into the video, watch expressions defined. Pay attention to how an expression is defined as anything that evaluates to a single value, like x / 2. Expressions and Statements Summary: Immediately after, at around six minutes in, watch expressions and statements. This part shows how an expression like x / 2 is part of a larger statement, int y = x / 2;. The semicolon at the end is what terminates the statement.
In C, the line is blurred in two key ways:
- Any expression can become a statement. You can write
5 + 10;as a line in a C program. It's a valid statement. The expression5 + 10is evaluated to15, and the result is then discarded. It's a useless statement, but syntactically valid. - Expressions can have side effects. The most common example is the assignment operator,
=. In C,x = 5is an expression that evaluates to the value5. Its side effect is that the variablexis now assigned that value. This allows for code likey = (x = 5);, which assigns5toxand then assigns the result of that expression (which is5) toy. This is a classic feature of C-family languages that is foreign to purely functional ones.
3. The Functional Tradition: Expression-Oriented Design
Functional languages, drawing their inspiration from mathematics (specifically lambda calculus), take a different approach. As you saw in the Mitchell slides on Lisp, these languages are expression-oriented. Almost everything is an expression that returns a value.
A program is not a sequence of commands to execute, but rather a single, large expression to be evaluated (which itself is composed of many smaller expressions).
Let's see how this works in Haskell, a modern, purely functional language.
Haskell for Imperative Programmers #2 - Functions, Types, let & where
This video, 'Haskell for Imperative Programmers', directly contrasts the functional and imperative ways of thinking about functions and values.
Please watch the following segments: Function Definitions and Expressions vs. Return Statements: Right from the start, watch function definitions. Notice the core idea: a Haskell function definition is an expression. There is no return statement; the value of the expression is the function's result. Let and Where Bindings as Expressions: A few minutes in, watch let/where bindings. This shows how even local variable bindings (let/where) are part of a larger expression structure, not separate statements.
The key insight here is that even control flow structures are expressions. Consider an if/else construct.
-
In a statement-oriented language like C or Python, it's a statement that directs control flow:
// C code int y; if (x > 5) { y = 10; // statement } else { y = 20; // statement }The
ifblock itself doesn't have a value; it contains statements that perform actions (assignment). -
In an expression-oriented language like Haskell or Lisp,
if/elseis an expression that evaluates to a value:-- Haskell code let y = if x > 5 then 10 else 20Here, the entire
if x > 5 then 10 else 20construct is an expression that evaluates to either10or20, and that resulting value is then bound toy.
This fundamental difference has profound implications. When everything is an expression that produces a value without side effects, reasoning about code becomes much more like reasoning about mathematical equations.
4. Summary and Key Takeaways
Let's consolidate what we've covered.
| Feature | Statement-Oriented (e.g., C) | Expression-Oriented (e.g., Lisp, Haskell) |
|---|---|---|
| Primary Unit | A statement that performs an action. | An expression that evaluates to a value. |
| Purpose | To issue commands and change state. | To transform data and compute values. |
| Control Flow | if, for, while are statements that direct execution. | if, case are expressions that evaluate to a value. |
| Side Effects | Common and central (e.g., assignment x=5 is a core operation). | Avoided or isolated. Assignment is a binding, not a mutable action. |
return | An explicit return statement is needed to give a value back from a function. | The function body is an expression; its value is implicitly returned. |
This distinction is one of the most important concepts separating the imperative and functional paradigms. Your experience with Python places you in an interesting middle ground. Python is primarily imperative and statement-oriented, but it has adopted many functional features. For example, it has list comprehensions and a ternary if operator (value_if_true if condition else value_if_false), which are both expression-oriented constructs.
Conclusion
In this lesson, you learned to distinguish between expressions and statements, a foundational concept in programming language theory.
- Expressions evaluate to a value.
- Statements perform an action.
- Imperative languages like C are built on a sequence of statements.
- Functional languages like Lisp and Haskell are built on the evaluation of expressions.
This shift from "doing things" to "evaluating to a value" is the essence of the functional mindset.
Preview of the Next Lesson:
In our next session, we will explore referential transparency. This is a direct and powerful consequence of the expression-oriented, side-effect-free style of programming. You'll see how it allows us to reason about code as if it were a mathematical equation, a property that is difficult to achieve in statement-oriented languages.
Can't find a good explanation? Sign up and we'll make it for you
Sign up