Reputation:
I am trying to write a function which loops 100 times and in this loop calls a function called priceJump. When the loop is first started the price argument to this function takes an initial value of 100. But after every loop, the function produces a new price which should be passed to the priceJump function.
I have tried to code this logic unsuccessfully (code below). The problem is that the code to produce a new price does so in a monadic way and so the value is of type m Double where m is constrained by the type class PrimMonad.
I'm probably not doing this in idiomatic Haskell which is why I am experiencing these problems so any pointers in the right direction would be appreciated also. To summarise the main goal is to call the pricing function with an initial price passed in and then for every new price it generates that new price should be passed back into it.
import Control.Monad.State
import Control.Monad.Primitive
import System.Random.MWC
import System.Random.MWC.Distributions
priceJump :: Double -- price
-> Double -- rate
-> Double -- vol
-> Double -- ts
-> Double -- rnd
-> Double -- new price
priceJump price rate vol ts rnd = price * (1 + (rate * ts) + (vol * sqrt(ts) * rnd))
loop :: PrimMonad m => Gen (PrimState m) -> Int -> Double -> Double -> Double -> Double -> m Double
loop gen turns price rate vol ts
| turns <= 0 = (priceJump price rate vol ts) <$> (normal 0 1 gen)
| turns == 100 = (priceJump price rate vol ts) <$> (normal 0 1 gen)
| otherwise = loop gen (turns - 1) (priceJump price rate vol ts) <$> (normal 0 1 gen) rate vol ts
Upvotes: 2
Views: 83
Reputation: 14623
Thinking in terms of loops in Haskell will only sometimes be useful. If you want to repeat something k
times, you should reach for replicate
and the like. In this case, you want to repeat the function which takes a price and produces a new price 100 times (and performs some computation in some monad), then sequence those all together in a long chain.
With this in mind, the code becomes
loop :: PrimMonad m => Gen (PrimState m) -> Int -> Double -> Double -> Double -> Double -> m Double
loop gen turns price rate vol ts
= foldr (>=>)
return
(replicate turns (\p -> priceJump p rate vol ts <$> normal 0 1 gen))
price
It turns out that Kleisli composition (>=> :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c
) does exactly what you need here. In this way you can see >=>
as being like regular function composition, but inside a monad.
Another option is to generate the random values in advance, then just deal with pure values from there - i.e pure fold over the list of random seeds. But the code doesn't end up looking that different:
loop gen turns price rate vol ts
= foldr (\rnd p -> priceJump p rate vol ts rnd) price <$>
replicateM turns (normal 0 1 gen)
Upvotes: 3