I'm trying to learn Haskell, so I decided to write a simple program to simulate the orbits of the planets around the sun, but I've run into a problem with printing out coordinates from the simulation, the top level function in my code is the following:
runSim :: [Body] -> Integer -> Double -> [Body]
runSim bodys 0 dtparam = bodys
runSim bodys numSteps dtparam = runSim (map (integratePos dtparam . integrateVel dtparam (calculateForce bodys)) (numSteps-1) dtparam
main = do
let planets = runSim [earth, sun] 100 0.05
print planets
A "Body" is just a data type holding the position, velocity etc of a planet, so the first parameter is just the list of planets in the simulation and the other parameters are the number of steps to integrate and the time step size respectively. My question is, how do I modify the code to print out the position of all bodys after each call to runsim? I tried adding a "printInfo" function to the composed functions passed to map like so:
printInfo :: Body -> Body
printInfo b = do
putStrLn b
b
but it doesn't compile, can anyone give me some hints?
Thanks!
Haskell separates pure functions from computations where side effects must be considered by encoding those side effects as values of a particular type. Specifically, a value of type (IO a) is an action, which if executed would produce a value of type a .
The IO type constructor provides a way to represent actions as Haskell values, so that we can manipulate them with pure functions. In the Prologue chapter, we anticipated some of the key features of this solution. Now that we also know that IO is a monad, we can wrap up the discussion we started there.
The I/O monad contains primitives which build composite actions, a process similar to joining statements in sequential order using `;' in other languages. Thus the monad serves as the glue which binds together the actions in a program.
yairchu has a good answer for your problem with printBody. Your central question, how to structure your program so that you can print out each step, is a little harder. Presumably you want to keep runSim
, or something like it, pure, since it's just running the simulation, and I/O isn't really its job.
There are two ways I would approach this: either make runSim return an infinite list of simulation steps, or make the I/O wrapper run only one step at a time. I prefer the first option, so I'll start with that.
Change runSim
to return a list of steps:
runSim :: [Body] -> Double -> [[Body]]
-- now returns a list of bodys, but no terminating condition
runSim bodys numSteps dtparam = nextBodys : runSim nextBodys dtparam
where nextBodys = map (integratePos dtparam . integrateVel dtparam)
(calculateForce bodys)
Now main
can take as many steps of the simulation as it wants and print them out:
main = mapM_ (mapM_ print) (take 100 $ runSim [earth, sun] 0.05)
Again, I'll assume that, following yairchu's advice, you have Body deriving Show
so that print
will work. mapM_
is like map
, except that it takes a monadic (here, side-effecting) function to map (ends with M) and doesn't return the list (ends with _). So really it's more like for-each
in Scheme or something.
The alternative is to keep your runSim
and write a print loop that only runs one step at a time:
printLoop :: Integer -> [Body] -> IO [Body]
printLoop 0 bodies = return bodies
printLoop n bodies = do
let planets = runSim bodies 1 0.05
mapM_ print planets -- still need to have Body deriving Show
printLoop (n-1) planets
main = do
printLoop 100 [earth, sun]
return ()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With