Chain of Responsibility with Function Composition in Haskell

Explore the Chain of Responsibility pattern using function composition in Haskell to create flexible and reusable pipelines for handling requests.

6.1 Chain of Responsibility with Function Composition

In this section, we delve into the Chain of Responsibility pattern, a behavioral design pattern that allows a request to be passed along a chain of handlers until one of them handles it. In Haskell, we can implement this pattern using function composition, creating a pipeline of functions that process requests in sequence. This approach leverages Haskell’s functional programming capabilities, such as higher-order functions and immutability, to build flexible and reusable systems.

Understanding the Chain of Responsibility Pattern

Intent: The Chain of Responsibility pattern decouples the sender of a request from its receiver by allowing multiple objects to handle the request. The request is passed along a chain of potential handlers until one of them handles it.

Key Participants:

  • Handler: Defines an interface for handling requests.
  • ConcreteHandler: Handles requests it is responsible for and forwards requests it does not handle to the next handler.
  • Client: Initiates the request to a handler in the chain.

Applicability:

  • Use this pattern when multiple objects can handle a request and the handler is not known a priori.
  • When you want to issue a request to one of several objects without specifying the receiver explicitly.
  • When the set of handlers and their order can change dynamically.

Implementing Chain of Responsibility in Haskell

In Haskell, we can implement the Chain of Responsibility pattern using function composition. This involves creating a series of functions that each take an input, perform some processing, and pass the result to the next function in the chain.

Function Composition in Haskell

Function composition in Haskell is achieved using the (.) operator, which allows us to combine two functions into a single function. For example, if we have two functions f and g, we can compose them as f . g, which is equivalent to \x -> f (g x).

 1-- Function composition example
 2f :: Int -> Int
 3f x = x + 1
 4
 5g :: Int -> Int
 6g x = x * 2
 7
 8-- Composed function
 9h :: Int -> Int
10h = f . g
11
12-- Usage
13main :: IO ()
14main = print (h 3) -- Output: 7

In this example, h is a composed function that first applies g to its input and then applies f to the result.

Building a Validation Chain

Let’s consider a practical example where we build a validation chain for user input. Each function in the chain checks a condition and passes the result to the next function.

 1-- Validation function type
 2type Validator a = a -> Either String a
 3
 4-- Validator that checks if a number is positive
 5positiveValidator :: Validator Int
 6positiveValidator x
 7  | x > 0     = Right x
 8  | otherwise = Left "Number must be positive"
 9
10-- Validator that checks if a number is even
11evenValidator :: Validator Int
12evenValidator x
13  | even x    = Right x
14  | otherwise = Left "Number must be even"
15
16-- Composing validators
17validateNumber :: Validator Int
18validateNumber = positiveValidator >=> evenValidator
19
20-- Usage
21main :: IO ()
22main = do
23  print $ validateNumber 4  -- Output: Right 4
24  print $ validateNumber (-2) -- Output: Left "Number must be positive"
25  print $ validateNumber 3  -- Output: Left "Number must be even"

In this example, we define two validators: positiveValidator and evenValidator. We then compose these validators using the >=> operator, which is the Kleisli composition operator for monads. This allows us to chain functions that return Either values, passing the result of one function as the input to the next.

Visualizing the Chain of Responsibility

To better understand the flow of requests through the chain, let’s visualize the process using a Mermaid.js diagram.

    graph TD;
	    A["Input"] --> B{positiveValidator}
	    B -- Right x --> C{evenValidator}
	    B -- Left "Number must be positive" --> D["Error"]
	    C -- Right x --> E["Success"]
	    C -- Left "Number must be even" --> D["Error"]

Diagram Description: The diagram illustrates the flow of a number through the validation chain. The input is first processed by positiveValidator. If the number is positive, it proceeds to evenValidator. If any validator fails, an error is returned.

Design Considerations

  • Flexibility: The Chain of Responsibility pattern allows you to add, remove, or reorder handlers dynamically, making it highly flexible.
  • Decoupling: By decoupling the sender and receiver, you can change the chain without affecting the client code.
  • Responsibility: Ensure that each handler in the chain has a clear responsibility to avoid confusion and maintainability issues.

Haskell Unique Features

Haskell’s strong type system and functional nature make it particularly well-suited for implementing the Chain of Responsibility pattern. The use of higher-order functions and monads allows for elegant composition and error handling.

Differences and Similarities

The Chain of Responsibility pattern is similar to the Decorator pattern in that both involve a series of operations applied to an object. However, the Chain of Responsibility pattern focuses on passing a request along a chain, while the Decorator pattern focuses on adding behavior to an object.

Try It Yourself

Experiment with the validation chain by adding new validators or modifying existing ones. For example, try adding a validator that checks if a number is less than 100.

 1-- Validator that checks if a number is less than 100
 2lessThanHundredValidator :: Validator Int
 3lessThanHundredValidator x
 4  | x < 100   = Right x
 5  | otherwise = Left "Number must be less than 100"
 6
 7-- Composing validators with the new validator
 8validateNumberWithLimit :: Validator Int
 9validateNumberWithLimit = positiveValidator >=> evenValidator >=> lessThanHundredValidator
10
11-- Usage
12main :: IO ()
13main = do
14  print $ validateNumberWithLimit 50  -- Output: Right 50
15  print $ validateNumberWithLimit 150 -- Output: Left "Number must be less than 100"

Knowledge Check

  • What is the primary purpose of the Chain of Responsibility pattern?
  • How does function composition help in implementing this pattern in Haskell?
  • What are the benefits of using the >=> operator for composing validators?

Summary

In this section, we explored the Chain of Responsibility pattern and its implementation in Haskell using function composition. We learned how to create a validation chain for user input and visualized the flow of requests through the chain. By leveraging Haskell’s functional programming features, we can build flexible and reusable systems that decouple the sender and receiver of requests.

Embrace the Journey

Remember, this is just the beginning. As you progress, you’ll build more complex and interactive systems using Haskell’s powerful design patterns. Keep experimenting, stay curious, and enjoy the journey!

Quiz: Chain of Responsibility with Function Composition

Loading quiz…
Revised on Thursday, April 23, 2026