2

这是弹跳球代码。我试图让'appendFile'在更新功能上运行,所以当球从墙上反弹时,'appendFile'会将px和px值写入文件“log.txt”

import Graphics.Gloss
import Graphics.Gloss.Data.ViewPort (ViewPort)

main :: IO ()
main =
  simulate
    (InWindow "Bouncing ball" (width, height) (50, 50))
    white
    30
    initial
    view
    update

但我遇到了麻烦,因为“appendFile”只在签名 IO 上运行。而且我不知道如何在这种情况下应用它

update :: ViewPort -> Float -> World -> World
update _ _ World {position = (px, py), velocity = (vx, vy)} =

  let 
      appendFile "Log.txt" ("" ++ show px ++ " " + show py ++ "")
      newPx = px + vx
      newPy = py + vy
      newVx = if newPx >= fromIntegral width || newPx <= 0 then - vx else vx
      newVy = if newPy >= fromIntegral height || newPy <= 0 then - vy else vy
   in World {position = (newPx, newPy), velocity = (newVx, newVy)}
4

1 回答 1

2

Haskell 对副作用非常严格。写入文件是一种副作用,纯函数(如您的update)不允许有副作用。

如果您只想记录数据以进行调试,那么您可以使用臭名昭著的 accursed unsafePerformIO,它为纯计算提供了进入 IO monad 的后门。名称中“不安全”位的原因是,它没有承诺 IO 操作的运行频率,或者即使它完全运行。

但是您上面的代码实际上不会调用appendFile. 事实上这是一个语法错误;alet引入了可能在代码中使用的值,但您没有为appendFile.

你需要更多类似的东西:

let
   ... omitted
in seq 
    (unsafePerformIO $ appendFile "Log.txt" (show px ++ " " ++ show py ++ "\n")
    World {position = (newPx, newPy), velocity = (newVx, newVy)}

seq是一个魔术函数,它的第一个参数是“严格的”,因此在unsafePerformIOnew 之前被评估World,即使没有使用第一个参数的结果。

然而,这是一个 kluge。您不应该unsafePerformIO用于生产代码。这是对编译器的谎言。如果你养成了它的习惯,那么编译器就会报仇雪恨,而且它不会很漂亮。

如果这是用于生产代码,那么您应该改用simulateIO. 这需要一个返回IO值的更新函数,因此您可以编写update返回一个IO World并且每个人都会很高兴。

于 2022-02-16T16:12:39.120 回答