JS.
JS.

Reputation: 616

How to structure Haskell code for IO?

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

Answers (4)

JS.
JS.

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

Nathan Shively-Sanders
Nathan Shively-Sanders

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

yairchu
yairchu

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

David Crawshaw
David Crawshaw

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:

http://learnyouahaskell.com/

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

Related Questions