Home > Blockchain >  Haskell gloss library, how to run appendFile on model signiture for log to file?
Haskell gloss library, how to run appendFile on model signiture for log to file?

Time:02-17

this is the bouncing ball code. I'm trying to make 'appendFile' run on the update function, so when the ball bounces off the wall, then 'appendFile' will write the px and px values to the file "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

but I'm having trouble because 'appendFile' only runs on signiture IO. And I don't know how to apply it in a situation like this

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)}

CodePudding user response:

Haskell is really strict about side effects. Writing to a file is a side effect, and a pure function (like your update) is not allowed to have side effects.

If you merely want to record the data for debugging then you can use the infamous accursed unsafePerformIO, which provides a back door into the IO monad for pure computations. The reason for the "unsafe" bit of the name is that this makes no promises about how often the IO action gets run, or even if it gets run at all.

BUT the code you have above won't actually call appendFile. In fact that is a syntax error; a let introduces values which might be used in the code, but you have no assignment for the result of appendFile.

You would need something more like:

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

seq is a magic function which is "strict" in its first argument, so the unsafePerformIO gets evaluated before the new World, even though nothing ever uses the result of that first argument.

However this is a kluge. You should not use unsafePerformIO for production code. It is a lie to the compiler. If you make a habit of it then the compiler will get its revenge, and it will not be pretty.

If this is for production code then you should instead use simulateIO. This takes an update function which returns an IO value, so then you can write update to return an IO World and everyone will be happy.

  • Related