[英]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
. b
是Body
类型但是需要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.