Create your own
Lesson illustration

The Evolution of First-Class Functions

Hello! Welcome back to our course on programming theory.

In our last few lessons, we've built up a picture of the functional paradigm by examining its core components: the distinction between expressions and statements, the principle of referential transparency, the discipline of writing pure functions, and the use of immutable data structures. We've seen how functional programming approaches the manipulation of data.

Today, we shift our focus to how it handles behavior. We will explore one of the most powerful and influential ideas in modern programming: treating functions not as second-class constructs, but as first-class citizens that can be manipulated just like any other value.

This lesson is designed to fulfill the following learning outcome:

Trace the historical development of first-class and higher-order functions from Lisp through modern languages.

Our journey will begin with the abstract mathematical origins of this idea, trace its pioneering implementation in Lisp, observe its parallel evolution in other language families like Algol, and conclude with its widespread adoption in the contemporary languages you use today, including Python, JavaScript, and even Java.

1. What are First-Class and Higher-Order Functions?

Before we begin our historical tour, let's establish our terms.

In a programming language, a value is considered a first-class citizen if it can be:

  • Assigned to a variable or stored in a data structure.
  • Passed as an argument to a function.
  • Returned as the result from a function.

In many early languages, things like integers and strings were first-class, but functions were not. A language is said to have first-class functions if it treats them as first-class citizens.

A higher-order function is a direct consequence of this. It is a function that either takes one or more functions as arguments, returns a function as its result, or both.

2. The Theoretical Foundation: Lambda Calculus

The idea of treating functions as computable values did not originate with programming languages, but with mathematical logic.

Conception, Evolution, and Application of Functional ...

To understand the origin, we'll turn to Paul Hudak's comprehensive paper, 'Conception, Evolution, and Application of Functional Programming Languages.' This section introduces the foundational system where functions are the central, and only, concept.

Please read Section 1.1, 'Lambda Calculus' (pages 363-367). As you read, focus on these points: Lambda calculus was designed to capture the computational aspects of functions. An 'abstraction' (e.g., λx.e) is the calculus's representation of a function. The concept of 'self-application' (a function applied to itself) is what gives the calculus its power, enabling recursion without explicit recursive definitions (via the Y combinator).

In Alonzo Church's lambda calculus, a lambda abstraction is not merely a definition; it is a value. Since the calculus is composed of nothing but functions, they are inherently first-class. This formal system, developed in the 1930s, provided the theoretical blueprint for anonymous functions and higher-order computation that would appear in programming languages decades later.

3. The Pioneer: Lisp

While lambda calculus provided the theory, Lisp, developed by John McCarthy in the late 1950s, was the first major programming language to put first-class functions into practice.

McCarthy's goal was pragmatic: to create a language for symbolic computation for AI research. He adopted Church's lambda notation for a practical purpose: to create anonymous functions on the fly.

Conception, Evolution, and Application of Functional ...

Let's read two perspectives on Lisp's contribution. First, Hudak's paper places Lisp in the historical timeline. Second, the course textbook, 'Concepts in Programming Languages,' highlights Lisp's core features.

First, read Section 1.2, 'Lisp' (pages 367-370) in Hudak's paper. Note McCarthy's contributions, especially the use of higher-order operations like mapcar.

Concepts in Programming Languages

Now, let's turn to the Mitchell textbook for a more language-focused view.

Read the slides titled 'LISP = LISt Processing', 'Some contributions of LISP', and 'Overview' (slides 42-45) covering core LISP concepts. Pay attention to how a program is just a list (an S-expression) and how the first element of a list is treated as a function to be called.

Lisp's mapcar is the canonical example of a higher-order function. It takes a function and a list as arguments and applies the function to each element of the list.

; Lisp example
(define (mapcar fun lst)
  (if (null lst)
      nil
      (cons (fun (car lst)) (mapcar fun (cdr lst)))))

; Usage: apply the anonymous function (lambda (x) (* x x)) to a list
(mapcar (lambda (x) (* x x)) '(1 2 3 4))
; ==> (1 4 9 16)

To make this work correctly, Lisp needed a mechanism to handle functions that used variables from their surrounding context. This mechanism is the closure. A closure is a data structure containing the function's code along with a reference to its lexical environment—the scope in which it was created. This ensures that the function behaves correctly, even when it's passed around and executed in a different context. The debate between dynamic and static (lexical) scoping, mentioned in the Mitchell text, was a pivotal point in language history; modern languages have almost universally adopted lexical scoping for its predictability.

4. Parallel Evolution and Refinement

The idea of treating functions as arguments was not confined to the Lisp world. The Algol family of languages, which heavily influenced imperative programming, also experimented with this concept.

Concepts in Programming Languages

The Mitchell textbook reveals a fascinating parallel development in Algol 60, along with a critical limitation that highlights the implementation challenges of the time.

Read the slide titled 'Algol innovations' (slide 56). Focus on the bullet point: 'Functions (closures) as arguments – but entertainingly not “closures as results”...'.

Algol 60 allowed functions to be passed as arguments but not returned as results. This was a direct consequence of its stack-based memory model. Closures were allocated on the stack. If a function could be returned, the stack frame containing its lexical environment would be deallocated, leaving the returned function with invalid references—a "dangling pointer" to the stack.

Lisp, with its heap-based allocation and garbage collection, did not have this limitation. This distinction is a classic example of how a language's memory model enables or constrains its features.

Later, languages in the ML family (including Standard ML and eventually Haskell) successfully merged the functional power of Lisp with the static typing discipline of Algol, creating robust type systems that could correctly infer the types of higher-order functions.

5. Mainstream Adoption

For decades, higher-order functions were seen as a feature of academic or "weird" functional languages. Over the last 20 years, that has changed dramatically. The power and expressiveness of this feature have led to its adoption across almost every major programming paradigm.

Scripting Languages

As you know from your experience with Python and JavaScript, first-class functions are fundamental to modern scripting.

  • Python: Functions are objects like any other. They can be passed to map, filter, stored in dictionaries, etc.
  • JavaScript: The influence is even more direct. As the Mitchell text notes, JavaScript's design was inspired by Scheme, a dialect of Lisp. Callbacks, event handlers, and promises are all built on the foundation of first-class functions.

Concepts in Programming Languages

The Mitchell textbook explicitly notes this lineage.

Quickly review slide 114, 'JavaScript', to read the language overview. Note the mention of 'higher-order functions' and the historical context.

Object-Oriented Languages

Perhaps the most significant sign of maturation is the adoption of functional concepts by traditionally object-oriented languages. Java's evolution is a prime case study.

For years, Java treated methods as "second-class citizens." They were tied to classes and could not be passed around freely. To pass a behavior, you had to wrap it in an object (the Strategy design pattern, often using an anonymous inner class), which was notoriously verbose.

Java 8 introduced a seismic shift.

First-class functions in Java 8

This video from Manning Publications clearly explains the transition in Java 8, showing how methods were elevated to first-class values.

Please watch the following segments: Understand the distinction between first-class values and 'second-class citizens' in pre-Java 8. See how method references (e.g., File::isHidden) and passing behavior as an argument (Predicate p) solve the problem of code duplication. Observe how lambda expressions provide a concise, anonymous syntax for defining these behaviors inline.

The introduction of lambda expressions and method references in Java, C#, and other OO languages represents the final step in the journey of first-class functions: from a theoretical curiosity in logic, to a pioneering feature in Lisp, to a cornerstone of modern, multi-paradigm software development. The benefits of abstraction, code reuse, and expressiveness proved too compelling to ignore.

Conclusion

Today we have traced the remarkable journey of an idea from abstract mathematics to ubiquitous programming practice.

Key Takeaways:

  • The concept of first-class functions—treating functions as values that can be passed, returned, and stored—originated in lambda calculus.
  • Lisp was the first major language to implement this idea, enabling higher-order functions like mapcar and introducing the crucial implementation detail of the closure.
  • The idea evolved in parallel, with languages like Algol 60 adopting parts of it (functions as arguments) but being constrained by their stack-based memory model.
  • The ML family integrated higher-order functions with powerful static type systems, a model followed by modern functional languages like Haskell.
  • Today, first-class functions are a core feature in nearly all modern languages, including Python, JavaScript, and have been explicitly added to OO languages like Java and C# to increase their expressive power.

Preview of the Next Lesson:
We have now covered the foundational principles of functional programming: pure functions, immutable data, and higher-order functions. In our next lesson, we will synthesize this knowledge to evaluate the paradigm as a whole. We will analyze the benefits and trade-offs of functional programming, using specific language examples (like Haskell's type safety vs. Python's flexibility) to understand when and why one might choose a functional approach.

Can't find a good explanation? Sign up and we'll make it for you

Sign up