Recommended resources
The freely available resources this lesson will direct you to.
Hello! Welcome to the first lesson in our module on Higher-Order Functional Patterns.
In our previous modules, we established the core tenets of functional programming, such as the importance of pure functions and the use of functions as first-class citizens. Today, we'll build directly on that foundation.
This lesson is about function composition, a powerful technique for building complex operations from simple, reusable parts. We'll explore its mathematical roots and see how it's implemented in practice. Specifically, we will compare the explicit approach you might take in a multi-paradigm language like Python with the elegant, built-in syntax provided by a functional-first language like Haskell.
By the end of this lesson, you will be able to:
- Compose functions to build complex operations.
- Compare explicit composition in Python (e.g.,
lambda x: f(g(x))) with Haskell's(.)operator.
Let's begin.
1. The Mathematical Foundation of Composition
Before we write any code, it's essential to understand the concept. Function composition is a direct import from mathematics. Given two functions, and , the composition (read as "f composed with g") produces a new function that applies first, and then applies to the result of .
To formalize this, please read the following brief introduction.
{
"intro": "This resource, from python-course.eu, provides a concise mathematical definition of function composition.",
"resource_title": "10. Function Composition In Python | Advanced",
"resource_id": "3874f",
"relevant_parts": [0],
"instructions": "Please read the section titled 'In Mathematics'. Focus on the definition $(f \\circ g)(x) = f(g(x))$ and the concrete example provided.",
"estimated_time": "5 minutes"
}
As you read, note the order of execution: in , the rightmost function () is applied first. This right-to-left associativity is a key characteristic.
An important property of function composition is that it is generally not commutative. That is, is not necessarily equal to . Your background in mathematics should make this intuitive—the order of operations matters.
2. Explicit Composition in Python
Python, with its support for first-class functions, allows us to implement composition directly. Since it's not a built-in language feature with a dedicated operator, we create a helper function that takes other functions as arguments and returns a new, composed function.
Let's look at how this is done.
The Rosetta Code wiki offers a very direct implementation of function composition in Python, which aligns perfectly with the learning outcome.
Navigate to the 'Python' section and study the first example under 'Simple composition of two functions'. It uses a lambda to define compose, which is a common and concise pattern.
The example compose = lambda f, g: lambda x: f(g(x)) is the heart of the matter.
- The outer lambda
lambda f, g:defines a function that accepts two functions,fandg, as arguments. - It returns another function, the inner lambda
lambda x: .... This returned function is the result of the composition. It "closes over"fandg. - This inner function takes the actual argument
x, appliesgto it, and then appliesfto that result.
This pattern is powerful because it treats functions as data, allowing you to create new functions dynamically.
For a slightly more verbose but perhaps more readable implementation using standard def statements, and to see how it can be generalized, please review the following.
10. Function Composition In Python | Advanced
This article from python-course.eu expands on the Python implementation, showing a named-function approach and generalizing it for functions with multiple arguments.
Read the sections 'Function Composition in Python' and '"compose" with Arbitrary Arguments'. Pay attention to how the compose function is defined using a nested function h, and how *args and **kwargs make it more robust.
Quick Exercise
Let's put this into practice. In a Python environment, define the following two functions:
def to_string(x):
return str(x)
def add_exclamation(s):
return s + "!"
Now, using the compose function pattern we just studied, create a new function shout_number that composes add_exclamation and to_string. When you call shout_number(42), it should return the string "42!".
3. Built-in Composition in Haskell
While Python requires an explicit helper function for composition, languages designed with functional programming as a core paradigm often provide a built-in operator. Haskell is a prime example.
In Haskell, the . (dot) operator performs function composition. The expression f . g creates a new function that is the composition of f and g.
This video provides a clear introduction.
{
"intro": "This video from Philipp Hagenlocher's 'Haskell for Imperative Programmers' series introduces the composition operator.",
"resource_id": "b35ee",
"relevant_parts": [0],
"instructions": "Watch the first part of the video (00:00 - 01:44). The key takeaway is the equivalence: `f . g` is the same as `\\x -> f (g x)` (where `\\` denotes a lambda in Haskell).",
"estimated_time": "5 minutes"
}
The Rosetta Code page you looked at earlier also confirms this. Under the Haskell section, it states that composition is already defined as the . operator.
This distinction is central to our comparison:
- Python (Explicit): Composition is a pattern you implement yourself, typically with a higher-order function like
compose. - Haskell (Implicit/Built-in): Composition is a fundamental operation, baked into the language's syntax.
This syntactic sugar encourages a style of programming where you think about building programs by plumbing functions together, rather than by writing sequences of statements.
4. The Power of Composition: A Deeper Example
So far, we've used simple examples. But the real power of composition shines when you build complex pipelines. Let's look at a more sophisticated example in Haskell: mapping a function over a 2D list (a list of lists).
An imperative programmer might write nested loops. A functional programmer might compose map with itself.
Haskell for Imperative Programmers #8 - Function Composition
The same video now provides a more advanced example that demonstrates the expressiveness of composition.
Please watch the next section of the video (01:44 - 05:55). The goal is not to understand every nuance of Haskell's type system, but to appreciate how map . map creates a 2D mapping function. Notice how the functions are applied one argument at a time.
The expression map . map creates a new function that takes a function f and a 2D list xss and applies f to every element in every sublist. This is remarkably expressive. A single line of code defines a complex operation by simply composing a generic function with itself.
This example works because of how Haskell handles functions with multiple arguments. map technically takes a function and a list. When you give it only a function (as in the first map in the composition), it returns a new function that is waiting for a list. This is a concept called currying, which is the topic of our next lesson.
Composing Multiple Functions
What if you want to compose more than two functions? The pattern extends naturally.
- In Haskell:
f . g . h - In Python:
compose(f, compose(g, h))
The nested calls in Python can become clumsy. A more idiomatic solution uses reduce from Python's functools module to apply composition across a sequence of functions.
The Rosetta Code resource demonstrates a clean way to compose multiple functions in Python.
Look at the 'Multiple composition' section under Python. Focus on the use of functools.reduce to create a generalized compose function that can take any number of functions.
This shows how you can build powerful, general-purpose tools in Python by combining higher-order functions like reduce with simple lambdas, emulating patterns that are built-in elsewhere.
Conclusion
In this lesson, we've explored function composition as a core pattern in functional programming.
Key Takeaways:
- Mathematical Roots: Function composition is the creation of a new function, , from two existing functions, and , such that . The order of application is right-to-left.
- Explicit vs. Built-in: In Python, composition is implemented explicitly as a higher-order function that takes functions as arguments and returns a new function (e.g.,
compose = lambda f, g: lambda x: f(g(x))). In Haskell, it's a built-in feature represented by the.operator, which encourages a declarative, pipeline-oriented style. - Declarative Power: Composition allows you to build complex logic by combining small, focused, and reusable functions. This improves code clarity and maintainability by separating concerns into distinct functional units.
Preview of the Next Lesson:
The map . map example hinted at something deeper. The ability to apply map to just one of its arguments and get a function back is a feature called currying. In our next lesson, we will dive into currying and its close relative, partial application, to see how they enable even more flexible and expressive functional patterns.