Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to structure Haskell code for IO?

Tags:

io

haskell

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!

like image 581
JS. Avatar asked Sep 20 '09 21:09

JS.


People also ask

How does Haskell handle IO?

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 .

Is IO a monad Haskell?

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.

Why is IO a monad in Haskell?

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.


1 Answers

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 ()
like image 130
Nathan Shively-Sanders Avatar answered Oct 26 '22 10:10

Nathan Shively-Sanders