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.