Bank Kata in PureScript

Posted on March 13, 2019 by Riccardo

Bank Kata in PureScript (Series)

Bank Kata in PureScriptTesting Bank Kata in PureScript

Intro

I suck at FP and I desperately need some feedback from you. So please, do not get mad at the code. And double please share feedback if you got any!!

The Kata

Let’s first introduce the kata by copy / pasting from the awesome Kata-Log:

Write a class Account that offers the following methods void deposit(int) void withdraw(int) String printStatement()

An example statement would be:

Date Amount Balance
24.12.2015 +500 500
23.8.2016 -100 400

Types

void deposit(int) and void withdraw(int) are impure functions. In fact, they accept an int and return void. The only way they can do anything useful is to mutate some state.

String printStatement() is impure too. As a matter of fact, it returns a string out of nothing. The only way for it to do anything useful is to access some state. In this post, I’ll implement printStatement as if it was void printStatement(). That is, the function will print the statement in the console. The reason is that I don’t know how to code it otherwise.

One way to read / write state in PureScript is using the state monad transformer (StateT).

Therefore, we will use the following types:

deposit :: Int -> StateT (Array Transaction) Effect Unit

withdraw :: Int -> StateT (Array Transaction) Effect Unit

printStatement :: StateT (Array Transaction) Effect Unit

In other words, our three functions will do their thing in the StateT (Array Transaction) Effect Unit environment. In simpler words, each function will be able to manipulate an array of transactions (state), write to console or get datetimes (monadic operations in Effect) and return nothing (Unit) at the end.

And here we have the type for Transaction:

data Transaction
 = Deposit Info
 | Withdraw Info

type Info =
 { timestamp :: DateTime
 , amount :: Int
 }

Implementation

Let’s start with deposit:

deposit :: Int -> StateT (Array Transaction) Effect Unit
deposit amount = do
 ts <- lift nowDateTime
 let t = Deposit { timestamp: ts, amount: amount }
 modify_ \ts -> ts <> [t]

Since we are in a monadic environment (StateT (Array Transaction) Effect Unit), we open the function with a do.

Then we use nowDateTime :: Effect DateTime to get the current datetime. The only catch here is that we need to first lift nowDateTime in StateT (Array Transaction) Effect Unit. That is because in a do block each monadic operation (i.e. non lets) must all use the same monad. In this case, that means that both lift nowDateTime and modify_ \ts -> ts <> [t] have type StateT (Array Transaction) Effect a.

After that, a deposit with correct timestamp and amount is assigned to t.

Lastly, modify_ is used to access the current state ts(array of transactions) by appending the new transaction t.

withdraw is almost the same:

withdraw :: Int -> StateT (Array Transaction) Effect Unit
withdraw amount = do
 ts <- lift nowDateTime
 let t = Withdraw { timestamp: ts, amount: amount }
 modify_ \ts -> ts <> [t]

Finally, we have printStatement:

printStatement :: StateT (Array Transaction) Effect Unit
printStatement = do
 s <- gets toStatement
 lift $ log s

The first line uses gets to take the state (array of transactions) and run it through toStatement :: Array Transaction -> String. That means gets toStatement has type Effect String and s has type String.

The last line lifts log s :: Effect Unit in StateT (Array Transaction) Effect Unit. In other words, it prints s to the console.

The implementation of toStatement is not that important. Here is an example of that:

toStatement :: Array Transaction -> String
toStatement =
 fst <<< foldl fnc (Tuple "" 0)
 where
 fnc (Tuple s i) (Deposit d) =
 Tuple (s <> "\n" <> joinWith " " [show d.timestamp, show d.amount, show $ i + d.amount]) (i + d.amount)
 fnc (Tuple s i) (Withdraw w) =
 Tuple (s <> "\n" <> joinWith " " [show w.timestamp, "-" <> show w.amount, show $ i - w.amount]) (i - w.amount)

Fire it up

Now we can write something like

do
 deposit 500
 withdraw 100
 printStatement

which has type StateT (Array Transaction) Effect Unit. And we can run that computation with evalStateT. Notice that the following code returns Effect Unit.

flip evalStateT [] do
 deposit 500
 withdraw 100
 printStatement

Show me the code

And here we have all the code

data Transaction
  = Deposit Info
  | Withdraw Info

type Info =
  { timestamp :: DateTime
  , amount    :: Int
  }

deposit :: Int -> StateT (Array Transaction) Effect Unit
deposit amount = do
  ts <- lift nowDateTime
  let t = Deposit { timestamp: ts, amount: amount }
  modify_ \ts -> ts <> [t]

withdraw :: Int -> StateT (Array Transaction) Effect Unit
withdraw amount = do
  ts <- lift nowDateTime
  let t = Withdraw { timestamp: ts, amount: amount }
  modify_ \ts -> ts <> [t]

printStatement :: StateT (Array Transaction) Effect Unit
printStatement = do
  s <- gets toStatement
  lift $ log s

toStatement :: Array Transaction -> String
toStatement =
  fst <<< foldl fnc (Tuple "" 0)
  where
  fnc (Tuple s i) (Deposit d) =
    Tuple (s <> "\n" <> joinWith " " [ show d.timestamp, show d.amount, show $ i + d.amount]) (i + d.amount)
  fnc (Tuple s i) (Withdraw w) =
    Tuple (s <> "\n" <> joinWith " " [ show w.timestamp, "-" <> show w.amount, show $ i - w.amount]) (i - w.amount)

main :: Effect Unit
main = do
  flip evalStateT [] do
    deposit 500
    withdraw 100
    printStatement

Outro

If you liked the post and want to help spread the word, please make some noise 🤘 But only if you really liked it. Otherwise, please feel free to comment or tweet me with any suggestions or feedback.

Thanks to Liam Griffin who inspired me to try this exercise in PureScript with his post in Haskell.

Finally, I want to give a shoutout to BusConf and to all the people I’ve met there that showed so much support for my PureScript journey. You are awesome!

If you are hungry for more, see how we can test the code in the followup: Testing Bank Kata in PureScript.

PinkLetter

It's one of the selected few I follow every week – Mateusz

Tired of RELEARNING webdev stuff?

  • A 100+ page book with the best links I curated over the years
  • An email once a week full of timeless software wisdom
  • Your recommended weekly dose of pink
  • Try before you buy? Check the archives.