haskell – Compute a Working Capital Loan


This code takes a list of EBITDA amounts (for a hypothetical business), and computes a loan that would fund that business with working capital in periods of loss. When the business has positive EBITDA again, the loan is paid off. The loan also charges interest.

Some specific questions I have

  • I am not sure the way wcLoanCalc calls wcLoanR is the most beautiful solution. Is there something more elegant I could do?
  • It feels awkward to have such huge statements to update the LoanPeriod type, but I like having a single type name and hold all this data. Is there a better way?
  • Is there a more general way I could implement printing out the table? Presumably there is some nice library for doing this

I’m grateful for any and all feedback!

-- Compute Working Capital Loan using Haskell

import Data.List
import Text.Printf

-- | Hold all information about a single period of loan amortization
data LoanPeriod = LoanPeriod
  { cashAvail :: Double
  , draw      :: Double
  , ds        :: Double
  , principal :: Double
  , interest  :: Double
  , balance   :: Double
  }

instance Show LoanPeriod
  where
    show lp = printf "%-10.2f %-10.2f %-10.2f %-10.2f %-10.2f %-10.2f"
                (cashAvail lp)
                (draw lp)
                (ds lp)
                (principal lp)
                (interest lp)
                (balance lp)

type WCLoanAmort = (LoanPeriod)

-- | Test the wcLoanCalc function
wcLoanTest :: WCLoanAmort
wcLoanTest = wcLoanCalc testEbitda testInterestRate
  where
    testEbitda = (100.0, 30.0, -50.0, -10.0, 20.0, 40.0, 60.0, -100.0, 120.0, 30.0)
    testInterestRate = 0.12

-- | Print the result of the wcLoanTest
wcLoanTestPrint :: IO ()
wcLoanTestPrint = loanTable wcLoanTest

-- | Print out a WCLoanAmort as a table
loanTable :: WCLoanAmort -> IO ()
loanTable lpList =
    putStrLn $
    printf "%-10s %-10s %-10s %-10s %-10s %-10sn"
              "Cash" "Draw" "DS" "Prin" "Int" "Bal" ++
    concat ("---------- " | x <- (1..6)) ++ "n" ++
    concat (show lp ++ "n" | lp <- lpList)

-- | Calculate a working capital loan
wcLoanCalc :: (Double) -> Double -> WCLoanAmort
wcLoanCalc ebitda interestRate =
    wcLoanR ebitda interestRate ()

-- | Recursively compute each period of working-capital loan
wcLoanR :: (Double) -> Double -> WCLoanAmort -> WCLoanAmort
wcLoanR (ebitda:es) interestRate loanData
    | es == () = newLoanData
    | otherwise = wcLoanR es interestRate newLoanData
  where
    startBalance  = case length loanData of
                        0 -> 0.0
                        _ -> balance (last loanData)
    newInterest   = startBalance * interestRate / 12.0
    newDraw       = - min ebitda 0.00
    newDS         = max 0.0 $ min ebitda (startBalance + newInterest)
    newPrincipal  = newDS - newInterest
    newBalance    = startBalance + newDraw - newPrincipal
    newLoanData   = loanData ++ (newLoanPeriod)
    newLoanPeriod = LoanPeriod
                      { cashAvail = ebitda
                      , draw      = newDraw
                      , ds        = newDS
                      , principal = newPrincipal
                      , interest  = newInterest
                      , balance   = newBalance}