简体   繁体   English

如何为IO构造Haskell代码?

[英]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: 我正在尝试学习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

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. “Body”只是一个保持行星位置,速度等的数据类型,因此第一个参数只是模拟中的行星列表,其他参数分别是要积分的步数和时间步长。 My question is, how do I modify the code to print out the position of all bodys after each call to runsim? 我的问题是,在每次调用runsim之后,如何修改代码以打印出所有身体的位置? I tried adding a "printInfo" function to the composed functions passed to map like so: 我尝试将“printInfo”函数添加到传递给map的组合函数中,如下所示:


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

but it doesn't compile, can anyone give me some hints? 但它没有编译,任何人都可以给我一些提示吗?

Thanks! 谢谢!

yairchu has a good answer for your problem with printBody. yairchu对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. 大概你想保持runSim或类似的东西,纯粹,因为它只是运行模拟,而I / O并不是真正的工作。

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. 有两种方法可以解决这个问题:要么让runSim返回一个无限的模拟步骤列表,要么让I / O包装器一次只运行一步。 I prefer the first option, so I'll start with that. 我更喜欢第一个选项,所以我将从那开始。

Change runSim to return a list of steps: 更改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)

Now main can take as many steps of the simulation as it wants and print them out: 现在main可以根据需要采取尽可能多的模拟步骤并将其打印出来:

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. 再次,我会假设,按照yairchu的建议,你有Body deriving Show这样print就可以了。 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 _). mapM_就像map一样,除了它需要mapM_ (这里是副作用)函数来映射(以M结尾)并且不返回列表(以_结尾)。 So really it's more like for-each in Scheme or something. 所以它真的更像for-each在Scheme或者什么东西。

The alternative is to keep your runSim and write a print loop that only runs one step at a time: 另一种方法是保留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 ()

Regarding 关于

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

Unless " type Body = String ", you can't do putStrLn to a Body . 除非“ type Body = String ”,否则不能将putStrLn作为Body

ghci> :t putStrLn
putStrLn :: String -> IO ()

putStrLn requires a String . putStrLn需要一个String You can use putStrLn . show 你可以使用putStrLn . show putStrLn . show , or putStrLn . show ,或

$ hoogle "Show a => a -> IO ()"
Prelude print :: Show a => a -> IO ()

use print . print

Now, making reasonable assumptions about the type Body , printInfo 's type is wrong. 现在,对Body类型做出合理的假设, printInfo的类型是错误的。 As it calls putStrLn it should end with "-> IO Something". 因为它调用putStrLn它应该以“ - > 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 . bBody类型但是需要IO Body东西。 How can we make this transformation? 我们怎样才能实现这种转变? Using return :: Monad m => a -> ma . 使用return :: Monad m => a -> ma

So here's a working version: 所以这是一个工作版本:

printBody :: Body -> IO Body
printBody b = do
  print b
  return b

To do IO, you have to be in the IO monad: 要做IO,你必须在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函数中调用此函数,它必须位于IO monad中:

runSim :: [Body] -> Integer -> Double -> IO [Body]

(Though there may be better ways of organizing your function.) (虽然可能有更好的方法来组织你的功能。)

This monad business is non-trivial. 这个monad业务并非平凡。 It is Haskell's greatest strength, but it is difficult to wrap your head around when you first encounter it. 这是Haskell最强大的力量,但是当你第一次遇到它时很难绕过它。 I suggest working through a tutorial, such as this one: 我建议通过一个教程,比如这个:

http://learnyouahaskell.com/ http://learnyouahaskell.com/

Specifically, this will get you started: 具体来说,这将让你开始:

http://learnyouahaskell.com/input-and-output 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). 有许多关于monad的教程更详细(写一个是每个人在他们掌握它们之后做的第一件事)。 The links from haskell.org are your friends. haskell.org的链接是你的好友。

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! 再次感谢所有人!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM