如何为IO构造Haskell代码?

JS.*_*JS. 5 io haskell

我正在尝试学习Haskell,所以我决定编写一个简单的程序来模拟太阳周围行星的轨道,但是我遇到了从模拟打印出坐标的问题,我的代码中的顶级函数如下:


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

"Body"只是一个保持行星位置,速度等的数据类型,因此第一个参数只是模拟中的行星列表,其他参数分别是要积分的步数和时间步长.我的问题是,在每次调用runsim之后,如何修改代码以打印出所有身体的位置?我尝试将"printInfo"函数添加到传递给map的组合函数中,如下所示:


printInfo :: Body -> Body
printInfo b = do
        putStrLn b
        b

但它没有编译,任何人都可以给我一些提示吗?

谢谢!

Nat*_*ers 7

yairchu对printBody的问题有一个很好的答案.您的核心问题是,如何构建程序以便打印出每个步骤,这有点困难.大概你想要保持runSim,或类似的东西,纯粹,因为它只是运行模拟,而I/O并不是真正的工作.

有两种方法可以解决这个问题:要么让runSim返回一个无限的模拟步骤列表,要么让I/O包装器一次只运行一步.我更喜欢第一个选项,所以我将从那开始.

更改runSim以返回步骤列表:

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)
Run Code Online (Sandbox Code Playgroud)

现在main可以根据需要采取尽可能多的模拟步骤并将其打印出来:

main = mapM_ (mapM_ print) (take 100 $ runSim [earth, sun] 0.05)
Run Code Online (Sandbox Code Playgroud)

再说一次,我会假设,按照yairchu的建议,你可以Body deriving Show这样print做.mapM_就像是map,除了它需要一个monadic(这里,副作用)函数来映射(以M结尾)并且不返回列表(以_结尾).所以真的更像是for-each在Scheme或者其他东西.

另一种方法是保持runSim并编写一个只能一次运行一步的打印循环:

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 ()
Run Code Online (Sandbox Code Playgroud)