Reputation: 1589
I want to use Haskell to solve a financial combinatorial problem, the list monad seems to be a good fit for this.
Now, my problem with the list monad is its inability to give names to the values involved. I will try to exemplify:
loan = [1000*x | x <- [1..3]]
interest_rate = [0.005*x | x <- [4..10]]
calc = do
l <- loan
i <- interest_rate
return (l*i)
Running calc above gives me a list of numbers ([20.0,25.0,30.0,35.0,40.0, ... ]
), but I can't tell what the loan and interest rate is used for each calculation.
I get lost here, my intuition tells me to create my own monadic type of, say HelpfulNumber :: (String,[Double])
and somehow say that:
>>=
and return
should be >>= . snd
and return . snd
Am I on the right course here, or is there a better way? I am feeling a bit lost to be honest.
Upvotes: 3
Views: 201
Reputation: 32455
You could use a record type to make your output clearer:
data Loan = Loan {final :: Double,
rate :: Double,
loan :: Integer,
years :: Int}
deriving Show
printloans :: [Loan] -> IO()
printloans = mapM_ print
Use printloans loans
or printloans loans'
at the ghci prompt.
Edit: I forgot to include the definition of dp
. It's for rounding to a given number of decimal places:
dp :: Int -> Double -> Double
n `dp` a = (/ 10.0^n).fromInteger.round.(* 10.0^n) $ a
Here's a way using a list directly:
loans = [Loan {final = (2 `dp`) $ fromInteger amt*(1+ir)^yrs,
rate = ir,
loan = amt,
years = yrs}
| ir <- [0.005*x | x <- [4..10]],
amt <- [1000*x | x <- [1..3]],
yrs <- [1..4]
]
But if you like the monadic style, you can use:
loans' = do
ir <- [0.005*x | x <- [4..10]]
amt <- [1000*x | x <- [1..3]]
yrs <- [1..4]
return Loan {final = (2 `dp`) $ fromInteger amt*(1+ir)^yrs,
rate = ir,
loan = amt,
years = yrs}
which benefits from fewer commas, and it's easier to change the order of the <-
lines to change the order of the answers.
You can add extras to your Loan
record and calculate with them.
You get output like this:
*Main> printloans loans'
Loan {final = 1020.0, rate = 2.0e-2, loan = 1000, years = 1}
Loan {final = 1040.4, rate = 2.0e-2, loan = 1000, years = 2}
Loan {final = 1061.21, rate = 2.0e-2, loan = 1000, years = 3}
Loan {final = 1082.43, rate = 2.0e-2, loan = 1000, years = 4}
Loan {final = 2040.0, rate = 2.0e-2, loan = 2000, years = 1}
Loan {final = 2080.8, rate = 2.0e-2, loan = 2000, years = 2}
...
...
EDIT:
You told me elsewhere you'd like output like ir_5% yrs_3 amt_4000 tot_4360.5
. It's uglier, but here's a way of doing that sort of thing:
loans'' = do
ir <- [0.005*x | x <- [4..10]]
amt <- [1000*x | x <- [1..3]]
yrs <- [1..4]
let final = (2 `dp`) $ fromInteger amt*(1+ir)^yrs
return $ "final_" ++ show final
++ ", ir_" ++ show ((2 `dp`) $ ir*100.0) -- rounded away a rounding error in 3.5%
++ "%, amt_" ++ show amt
++ ", yrs_" ++ show yrs
When you do mapM_ putStrLn loans''
you get output like
final_1020.0, ir_2.0%, amt_1000, yrs_1
final_1040.4, ir_2.0%, amt_1000, yrs_2
final_1061.21, ir_2.0%, amt_1000, yrs_3
final_1082.43, ir_2.0%, amt_1000, yrs_4
final_2040.0, ir_2.0%, amt_2000, yrs_1
....
but I think the record type is much nicer - its output is easier to read and there's less messing about with strings.
Upvotes: 6
Reputation: 10781
Why don't you just make two helper functions?
getName (loan, rate) = "loan="++loan++"&rate="++rate
getAnswer (loan, rate) = loan*rate
Then use a list comprehension
loans = [1000*x | x <- [1..3]]
interest_rates = [0.005*x | x <- [4..10]]
input_tuples = [(l, i) | l<-loans, i<-interest_rates]]
answers = [(getName t, getAnswer t) | t<-input_tuples]]
No monads necessary, not even list monad.
Upvotes: 2
Reputation: 26167
I don't know exactly what you're looking for here, because how would you give names to interest rates?
But you can of course store the interest rate along with the final result:
calc = do
l <- loan
i <- interest_rate
return (i, l*i)
-- Yields: [(0.02, 20.0), (0.025, 25.0), ...]
Upvotes: 1