Reputation: 616
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!
Upvotes: 5
Views: 1161
Reputation: 616
For the record, here is the solution I came up with, I think I'll recode it with infinite lists now tho:
runSim :: ([Body], [IO ()]) -> Integer -> Double -> ([Body], [IO ()])
runSim (bodys,bodyActions) 0 dtparam = (bodys, bodyActions)
runSim (bodys,bodyActions) numSteps dtparam = runSim (movedBodys, newBodyActions) (numSteps-1) dtparam
where movedBodys = (map (integratePos dtparam . integrateVel dtparam) (calculateForce bodys))
newBodyActions = bodyActions ++ map print bodys
main = do
let planets = runSim ([earth, sun],[]) 100 0.05
sequence $ snd planets
Thanks again all!
Upvotes: 1
Reputation: 18389
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 ()
Upvotes: 7
Reputation: 24754
Regarding
printInfo :: Body -> Body
printInfo b = do
putStrLn b
b
Unless "type Body = String
", you can't do putStrLn
to a Body
.
ghci> :t putStrLn
putStrLn :: String -> IO ()
putStrLn
requires a String
. You can use putStrLn . show
, or
$ hoogle "Show a => a -> IO ()"
Prelude print :: Show a => a -> IO ()
use print
.
Now, making reasonable assumptions about the type Body
, printInfo
's type is wrong. As it calls putStrLn
it should end with "-> IO Something".
Here:
printBody :: Body -> IO Body
printBody b = do
print b
b
Now the last line here is wrong. b
is of type Body
but stuff there needs to be of IO Body
. How can we make this transformation? Using return :: Monad m => a -> m a
.
So here's a working version:
printBody :: Body -> IO Body
printBody b = do
print b
return b
Upvotes: 4
Reputation: 10577
To do IO, you have to be in the IO monad:
printInfo :: Body -> IO Body
printInfo b = do
putStrLn b
return b
And to call this function from within your runSim
function, it to must be inside the IO monad:
runSim :: [Body] -> Integer -> Double -> IO [Body]
(Though there may be better ways of organizing your function.)
This monad business is non-trivial. It is Haskell's greatest strength, but it is difficult to wrap your head around when you first encounter it. I suggest working through a tutorial, such as this one:
Specifically, this will get you started:
http://learnyouahaskell.com/input-and-output
There are many tutorials on monads out there that go into much more detail (writing one is the first thing everyone does after they come to grips with them). The links from haskell.org are your friends.
Upvotes: 2