Bank kata in Haskell — dealing with state

In OO languages you usually have some sort of transaction repository that you add deposits and withdrawals to, and this stores your transactions.

It might look something like this:

This repository pattern is used to encapsulate the state, either in memory or from a database. In functional programming, you cannot encapsulate state or you lose it. Anything that is not returned from a function is lost. So, how do we deal with storing them?

Starting with the domain — adding deposits and withdrawals

First we’ll define a type for our transactions:

data Transaction = Deposit Int | Withdrawal Int

A Transaction can either be a Deposit or a Withdrawal. This is a sum type in Haskell, which you can relate to an enum in a static object-oriented language. They both wrap an Int which is the value of the transaction. Let’s think about what we want our deposit function to look like.

deposit :: Int -> [Transaction] -> [Transaction]

deposit takes an amount to deposit, a list of Transactions to add the deposit to, and because we can’t not return our list or we lose it — returns us a list of Transactions with the new Deposit added to it. withdraw would look very similar.

Withdraw will have the same function signature, but the body will add a Withdrawal.

Making a statement

getStatement :: [Transaction] -> String

getStatement will take our transactions and, for simplicity, return a nicely formatted string which will be our statement. And here we see the crux of the problem: once we return String we lose our list of transactions! Looks like we’ll have to return them too.

getStatement :: [Transaction] -> (String, [Transaction])

Now we return a tuple of String and [Transaction], so we can use them somewhere else if needs be. I’ll leave the function body as it’s out of the scope of this article. For testing:

useMyBank will return us the same tuple type as getStatement so we can use the statement and transactions somewhere else.

We can start to see how this will be cumbersome to use. We can also see dupllication in the structure of dealing with our functions, particularly demonstrated by transaction1/2/x naming. Let’s fix this.

Reshape our functions to expose duplication

There’s a pattern we’re going to refactor to, called the State monad. In order to do this we’ll need our functions to have a similar signature. So let’s change depositing and withdrawing to match getStatement:

Now what is returned from our functions is similar in all cases — (answer, [Transaction]), where answer might be a string in case of getStatement, or nothing in case of just adding a deposit/withdrawal. Using these functions looks similar:

Refactoring to the State monad

State is just a wrapper around our function, and the a can change depending on if we are storing a transaction or getting a statement. So how do we use it? We have a type constructor State that takes a function of signature s -> (a, s) and returns us a State type.

What advantages do we get from using State? It encapsulates the storing of state as a side effect, so we can focus on the domain of our code (adding deposits/withdrawals, generating statements) without worrying about the structure of storing state.

Next up, notice that our deposit and withdraw methods return () as an answer because there’s nothing to return. There is a helper function to wrap this called modify, that’s lets you change the state without caring about the return value.

The power of these functions is that I can extract what I really care about for my domain (e.g. generateStatement) and test that separately, without worrying about any state structure.

Next time

Wonderful! But wait, we are not matching the requirements, we’re returning the statement to the user and giving them the responsibility of printing it. Printing something involves using the IO monad, which means we need to combine two monads. This is not straightforward if you haven’t tried it before, so I’ll explain it in my next post.