A major benefit of Functional Programming is the separation of decisions and (side-)effects. Sometimes, however, one decision ought to trigger an impure operation, and then proceed to make more decisions. Using functional composition, you can succinctly conditionally compose functions.

In my article on how Functional Architecture falls into the Ports and Adapters pit of success, I describe how Haskell forces you to separate concerns:

  • Your Domain Model should be pure, with business decisions implemented by pure functions. Not only does it make it easier for you to reason about the business logic, it also has the side-benefit that pure functions are intrinsically testable.
  • Side-effects, and other impure operations (such as database queries) can be isolated and implemented as humble functions.
A common pattern that you can often use is:
  1. Read some data using an impure query.
  2. Pass that data to a pure function.
  3. Use the return value from the pure function to perform various side-effects. You could, for example, write data to a database, send an email, or update a user interface.
Sometimes, however, things are more complex. Based on the answer from one pure function, you may need to query an additional data source to gather extra data, call a second pure function, and so on. In this article, you'll see one way to accomplish this.

Caravans for extra space #

Based on my previous restaurant-booking example, Martin Rykfors suggests "a new feature request. The restaurant has struck a deal with the local caravan dealership, allowing them to rent a caravan to park outside the restaurant in order to increase the seating capacity for one evening. Of course, sometimes there are no caravans available, so we'll need to query the caravan database to see if there is a big enough caravan available that evening:"

findCaravan :: ServiceAddress -> Int -> ZonedTime -> IO (Maybe Caravan)

The above findCaravan is a slight modification of the function Martin suggests, because I imagine that the caravan dealership exposes their caravan booking system as a web service, so the function needs a service address as well. This change doesn't impact the proposed solution, though.

This problem definition fits the above general problem statement: you'd only want to call the findCaravan function if checkCapacity returns Left CapacityExceeded.

That's still a business decision, so you ought to implement it as a pure function. If you (for a moment) imagine that you have a Maybe Caravan instead of an IO (Maybe Caravan), you have all the information required to make that decision:

checkCaravanCapacityOnError :: Error
                            -> Maybe Caravan
                            -> Reservation
                            -> Either Error Reservation
checkCaravanCapacityOnError CapacityExceeded (Just caravan) reservation =
  if caravanCapacity caravan < quantity reservation
  then Left CapacityExceeded
  else Right reservation
checkCaravanCapacityOnError err _ _ = Left err

Notice that this function not only takes Maybe Caravan, it also takes an Error value. This encodes into the function's type that you should only call it if you have an Error that originates from a previous step. This Error value also enables the function to only check the caravan's capacity if the previous Error was a CapacityExceeded. Error can also be ValidationError, in which case there's no reason to check the caravan's capacity.

This takes care of the Domain Model, but you still need to figure out how to get a Maybe Caravan value. Additionally, if checkCaravanCapacityOnError returns Right Reservation, you'd probably want to reserve the caravan for the evening. You can imagine that this is possible with the following function:

reserveCaravan :: ServiceAddress -> ZonedTime -> Caravan -> IO ()

This function reserves the caravan at the supplied time. In order to keep the example simple, you can imagine that the provided ZonedTime indicates an entire day (or evening), and not just an instant.

Composition of caravan-checking #

As a first step, you can compose an impure function that

  1. Queries the caravan dealership for a caravan
  2. Calls the pure checkCaravanCapacityOnError function
  3. Reserves the caravan if the return value was Right Reservation
Notice how these steps follow the impure-pure-impure pattern I described above. You can compose such a function like this:

import Control.Monad (forM_)
import Control.Monad.Trans (liftIO)
import Control.Monad.Trans.Either (EitherT(..), hoistEither)

checkCaravan :: Reservation -> Error -> EitherT Error IO Reservation
checkCaravan reservation err = do
  c <- liftIO $ findCaravan svcAddr (quantity reservation) (date reservation)
  newRes <- hoistEither $ checkCaravanCapacityOnError err c reservation
  liftIO $ forM_ c $ reserveCaravan svcAddr (date newRes)
  return newRes

It starts by calling findCaravan by closing over svcAddr (a ServiceAddress value). This is an impure operation, but you can use liftIO to make c a Maybe Caravan value that can be passed to checkCaravanCapacityOnError on the next line. This function returns Either Error Reservation, but since this function is defined in an EitherT Error IO Reservation context, newRes is a Reservation value. Still, it's important to realise that exactly because of this context, execution will short-circuit at that point if the return value from checkCaravanCapacityOnError is a Left value. In other words, all subsequent expression are only evaluated if checkCaravanCapacityOnError returns Right. This means that the reserveCaravan function is only called if a caravan with enough capacity is available.

The checkCaravan function will unconditionally execute if called, so as the final composition step, you'll need to figure out how to compose it into the overall postReservation function in such a way that it's only called if checkCapacity returns Left.

Conditional composition #

In the previous incarnation of this example, the overall entry point for the HTTP request in question was this postReservation function:

postReservation :: ReservationRendition -> IO (HttpResult ())
postReservation candidate = fmap toHttpResult $ runEitherT $ do
  r <- hoistEither $ validateReservation candidate
  i <- liftIO $ getReservedSeatsFromDB connStr $ date r
  hoistEither $ checkCapacity 10 i r
  >>= liftIO . saveReservation connStr

Is it possible to compose checkCaravan into this function in such a way that it's only going to be executed if checkCapacity returns Left? Yes, by adding to the hoistEither $ checkCapacity 10 i r pipeline:

import Control.Monad.Trans (liftIO)
import Control.Monad.Trans.Either (EitherT(..), hoistEither, right, eitherT)

postReservation :: ReservationRendition -> IO (HttpResult ())
postReservation candidate = fmap toHttpResult $ runEitherT $ do
  r <- hoistEither $ validateReservation candidate
  i <- liftIO $ getReservedSeatsFromDB connStr $ date r
  eitherT (checkCaravan r) right $ hoistEither $ checkCapacity 10 i r
  >>= liftIO . saveReservation connStr

Contrary to F#, you have to read Haskell pipelines from right to left. In the second-to-last line of code, you can see that I've added eitherT (checkCaravan r) right to the left of hoistEither $ checkCapacity 10 i r, which was already there. This means that, instead of binding the result of hoistEither $ checkCapacity 10 i r directly to the saveReservation composition (via the monadic >>= bind operator), that result is first passed to eitherT (checkCaravan r) right.

The eitherT function composes two other functions: the leftmost function is invoked if the input is Left, and the right function is invoked if the input is Right. In this particular example, (checkCaravan r) is the closure being invoked in the Left - and only in the Left - case. In the Right case, the value is passed on unmodified, but elevated back into the EitherT context using the right function.

(BTW, the above composition has a subtle bug: the capacity is still hard-coded as 10, even though reserving extra caravans actually increases the overall capacity of the restaurant for the day. I'll leave it as an exercise for you to make the capacity take into account any reserved caravans. You can download all of the source code, if you want to give it a try.)

Interleaving #

Haskell has strong support for composition of functions. Not only can you interleave pure and impure code, but you can also do it conditionally. In the above example, the eitherT function holds the key to that. The overall flow of the postReservation function is:

  1. Validate the input
  2. Get already reserved seats from the database
  3. Check the reservation request against the restaurant's remaining capacity
  4. If the capacity is exceeded, attempt to reserve a caravan of sufficient capacity
  5. If one of the previous steps decided that the restaurant has enough capacity, then save the reservation in the database
  6. Convert the result (whether it's Left or Right) to an HTTP response
Programmers who are used to implementing such solutions using C#, Java, or similar languages, may feel hesitant by delegating a branching decision to a piece of composition code, instead of something they can unit test.

Haskell's type system is remarkably helpful here. Haskell programmers often joke that if it compiles, it works, and there's a morsel of truth in that sentiment. Both functions used with eitherT must return a value of the same type, but the left function must be a function that takes the Left type as input, whereas the right function must be a function that takes the Right type as input. In the above example, (checkCaravan r) is a partially applied function with the type Error -> EitherT Error IO Reservation; that is: the input type is Error, so it can only be composed with an Either Error a value. That matches the return type of checkCapacity 10 i r, so the code compiles, but if I accidentally switch the arguments to eitherT, it doesn't compile.

I find it captivating to figure out how to 'click' together such interleaving functions using the composition functions that Haskell provides. Often, when the composition compiles, it works as intended.


Comments

This is something of a tangent, but I wanted to hint to you that Haskell can help reduce the boilerplate it takes to compose monadic computations like this.

The MonadError class abstracts monads which support throwing and catching errors. If you don't specify the concrete monad (transformer stack) a computation lives in, it's easier to compose it into a larger environment.

checkCaravanCapacityOnError :: MonadError Error m
                            => Error
                            -> Maybe Caravan
                            -> Reservation
                            -> m Reservation
checkCaravanCapacityOnError CapacityExceeded (Just caravan) reservation
  | caravanCapacity caravan < quantity reservation = throwError CapacityExceeded
  | otherwise = return reservation
checkCaravanCapacityOnError err _ _ = throwError err

I'm programming to the interface, not the implementation, by using throwError and return instead of Left and Right. This allows me to dispense with calls to hoistEither when I come to call my function in the context of a bigger monad:

findCaravan :: MonadIO m => ServiceAddress -> Int -> ZonedTime -> m (Maybe Caravan)
reserveCaravan :: MonadIO m => ServiceAddress -> ZonedTime -> m ()

checkCaravan :: (MonadIO m, MonadError Error m) => Reservation -> Error -> m Reservation
checkCaravan reservation err = do
  c <- findCaravan svcAddr (quantity reservation) (date reservation)
  newRes <- checkCaravanCapacityOnError err c reservation
  traverse_ (reserveCaravan svcAddr (date newRes)) c
  return newRes

Note how findCaravan and reserveCaravan only declare a MonadIO constraint, whereas checkCaravan needs to do both IO and error handling. The type class system lets you declare the capabilities you need from your monad without specifying the monad in question. The elaborator figures out the right number of calls to lift when it builds the MonadError dictionary, which is determined by the concrete type you choose for m at the edge of your system.

A logical next step here would be to further constrain the effects that a given IO function can perform. In this example, I'd consider writing a separate class for monads which support calling the caravan service: findCaravan :: MonadCaravanService m => ServiceAddress -> Int -> ZonedTime -> m (Maybe Caravan). This ensures that findCaravan can only call the caravan service, and not perform any other IO. It also makes it easier to mock functions which call the caravan service by writing a fake instance of MonadCaravanService.

F# doesn't support this style of programming because it lacks higher-kinded types. You can't abstract over m; you have to pick a concrete monad up-front. This is bad for code reuse: if you need to add (for example) runtime configuration to your application you have to rewrite the implementation of your monad, and potentially every function which uses it, rather than just tacking on a MonadReader constraint to the functions that need it and adding a call to runReaderT at the entry point to your application.

Finally, monad transformers are but one style of effect typing; extensible-effects is an alternative which is gaining popularity.

2016-07-04 12:05 UTC

Hi Mark,

Thank you very much for the elaborate explanation. I'm also delighted that you stuck with my admittedly contrived example of using caravans to compensate for the restaurant's lack of space. Or is that nothing compared to some of the stranger real-life feature requests some of us have seen?

I agree with your point on my previous comment that my suggestion could be considered a leaky abstraction and would introduce unnecessary requirements to the implementation. It just feels strange to let go of the idea that the domain logic is to be unconditionally pure, and not just mostly pure with the occasional impure function passed in as an argument. It's like what you say towards the end of the post - I feel hesitant to mix branching code and composition code together. The resulting solution you propose here is giving me second thoughts though. The postReservation function didn't become much more complex as I'd feared, with the branching logic nicely delegated to the eitherT function. The caravan logic also gets its own composition function that is easy enough to understand on its own. I guess I've got some thinking to do.

So, a final question regarding this example: To what extent would you apply this technique when solving the same problem in F#? It seems that we are using an increasing amount of Haskell language features not present in F#, so maybe not everything would translate over cleanly.

2016-07-04 19:05 UTC

Martin, I'm still experimenting with how that influences my F# code. I'd like to at least attempt to back-port something like this to F# using computation expressions, but it may turn out that it's not worth the effort.

2016-07-04 20:46 UTC


Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or somewhere else with a permalink. Ping me with the link, and I may respond.

Published

Monday, 04 July 2016 06:53:00 UTC

Tags



"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!
Published: Monday, 04 July 2016 06:53:00 UTC