Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid operating in the IO monad for this whole program?

Tags:

io

haskell

I'm writing a Sokoban program in Haskell using the Gloss library, and I'm reaching the point where I'd like to, when the player beats a level, load in a new level from a text file and have the program continue on.

I'm having a little bit of difficulty with this because of Gloss's limitations -- the play function to set up a game and have it continuously update looks like this:

play    :: forall world
        .  Display                      -- ^ Display mode.
        -> Color                        -- ^ Background color.
        -> Int                          -- ^ Number of simulation steps to take for each second of real time.
        -> world                        -- ^ The initial world.
        -> (world -> Picture)           -- ^ A function to convert the world a picture.
        -> (Event -> world -> world)    -- ^ A function to handle input events.
        -> (Float -> world -> world)    -- ^ A function to step the world one iteration.
                                        --   It is passed the period of time (in seconds) needing to be advanced.
        -> IO ()

(Copied directly from http://hackage.haskell.org/packages/archive/gloss/1.7.4.1/doc/html/Graphics-Gloss-Interface-Pure-Game.html)

Here's the world type I'm using:

data Game = Game
  { levelNumber  :: Int,
    currentLevel :: Level Square,
    won          :: Bool }

where Levels contain the blocks in the current level. I'm reading in Games using something like this (haven't actually made a generalized one yet, but this is essentially all it would be with a filename argument):

startGame = do
  lvl <- readFile "levels/level001.lvl"
  let lvl' = parseLevel lvl
  return $ Game 1 lvl' False

So, my difficulty is arising because of the update functions in play. I can easily take a Game and produce a Picture (and a Game, etc) without having to read any data in from the file system if I'm just operating on a single level, but since I'm loading levels from files during the middle of the game, I don't know how to avoid making all of my Games IO Games. Maybe this isn't possible in this circumstance, and maybe that's for a good reason? I will always be operating on a Game pulled from a file but I don't know if it's avoidable at any given point, and if it is, I'd like to avoid it.

like image 730
Benjamin Kovach Avatar asked Mar 28 '13 20:03

Benjamin Kovach


People also ask

What is the IO Monad?

So, What is an IO Monad? IO Monad is simply a Monad which: Allows you to safely manipulate effects. Transform the effects into data and further manipulate it before it actually gets evaluated.

Why is IO a Monad in Haskell?

The I/O monad contains primitives which build composite actions, a process similar to joining statements in sequential order using `;' in other languages. Thus the monad serves as the glue which binds together the actions in a program.

How does Haskell avoid side effects?

The functional language Haskell eliminates side effects such as I/O and other stateful computations by replacing them with monadic actions. Functional languages such as Standard ML, Scheme and Scala do not restrict side effects, but it is customary for programmers to avoid them.

What is IO () Haskell?

IO is the way how Haskell differentiates between code that is referentially transparent and code that is not. IO a is the type of an IO action that returns an a . You can think of an IO action as a piece of code with some effect on the real world that waits to get executed.


1 Answers

I ended up using Gloss's playIO from Graphics.Gloss.Interface.IO.Game. I needed to change my a couple of my functions to operate on pure data types and output them wrapped in the IO monad. Here are the types I had to change:

worldToPicture :: World -> IO Picture
eventHandler   :: Event -> World -> IO Picture
stepWorld      :: Float -> World -> IO World 

For the most part, this only resulted in adding some returns to my currently existing functions, but it ended up adding a LOT of functionality (like saving on the fly, loading new levels, using BMP files for graphics, etc). I was also able to keep almost all of my currently existing code free from IO since the new functions still took pure data types as parameters. It ended up being a really easy refactor and solved my problem perfectly.

like image 104
Benjamin Kovach Avatar answered Nov 15 '22 06:11

Benjamin Kovach