【问题标题】:How to structure Haskell code for IO?如何为 IO 构建 Haskell 代码?
【发布时间】:2009-09-20 21:32:43
【问题描述】:

我正在尝试学习 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 后打印出所有主体的位置?我尝试向传递给 map 的组合函数添加一个“printInfo”函数,如下所示:


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

但它不能编译,谁能给我一些提示?

谢谢!

【问题讨论】:

    标签: haskell io


    【解决方案1】:

    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)
    

    现在main 可以根据需要执行任意多个模拟步骤并将其打印出来:

    main = mapM_ (mapM_ print) (take 100 $ runSim [earth, sun] 0.05)
    

    我再次假设,按照 yairchu 的建议,您拥有Body deriving Show,这样print 就可以工作。 mapM_map 类似,不同之处在于它需要一个单子(此处为副作用)函数来映射(以 M 结尾)并且不返回列表(以 _ 结尾)。所以实际上它更像是 Scheme 中的for-each 或其他东西。

    另一种方法是保留您的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 ()
    

    【讨论】:

    • 谢谢,这正是我所需要的!是的,无限列表方法似乎是最干净的。
    【解决方案2】:

    关于

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

    除非“type Body = String”,否则您不能对Body 执行putStrLn

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

    putStrLn 需要 String。你可以使用putStrLn . show,或者

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

    使用print

    现在,对BodyprintInfo 的类型做出合理的假设是错误的。当它调用putStrLn 时,它应该以“-> IO Something”结尾。

    这里:

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

    现在这里的最后一行是错误的。 bBody 类型,但里面的东西必须是 IO Body。我们如何才能进行这种转变?使用return :: Monad m => a -> m a

    所以这是一个工作版本:

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

    【讨论】:

    • 谢谢,很有帮助,我想我需要去更深入地研究 monads 和 IO,然后再回到这个。
    【解决方案3】:

    要做 IO,你必须在 IO monad:

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

    要从 runSim 函数中调用此函数,它必须在 IO monad 中:

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

    (虽然可能有更好的方法来组织你的功能。)

    这个 monad 业务是不平凡的。它是 Haskell 最大的优势,但是当你第一次遇到它时,你很难绕过去。我建议完成一个教程,比如这个:

    http://learnyouahaskell.com/

    具体来说,这会让你开始:

    http://learnyouahaskell.com/input-and-output

    那里有很多关于 monad 的教程,其中包含更详细的内容(编写一个是每个人在掌握它们后的第一件事)。来自 haskell.org 的链接是您的朋友。

    【讨论】:

    • 谢谢,我会去阅读更多内容。只是厌倦了理论,想弄湿我的脚:-)
    【解决方案4】:

    为了记录,这是我想出的解决方案,我想我现在用无限列表重新编码它:

    
    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

    再次感谢大家!

    【讨论】:

      猜你喜欢
      • 2014-03-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-03
      • 2011-07-11
      • 2019-03-28
      • 2015-11-13
      • 2019-07-18
      相关资源
      最近更新 更多