haskellgloss

Can you set the field of a record using an IO function (inside a do statement)?


In my main function for my haskell game, I start by loading each sprite. I decided to make a data type called SpriteCollection which holds all the sprites (typed Picture) so I can access them later by their name.

loadBMP is a Gloss function typed String -> IO Picture. This code works:

main = do
  -- Load sprites
  playerBmp <- loadBMP "assets/mario.bmp"
  goombaBmp <- loadBMP "assets/goomba.bmp"
  brickBmp <- loadBMP "assets/brick.bmp"
  let sprites = SpriteCollection { 
      marioSprite = playerBmp,
      goombaSprite = goombaBmp
      brickSprite = brickBmp
    }
  playIO (... sprites ...)

So as you can see, as I load more sprites, it feels like I am writing double the code that I should need. Is there a way to get rid of the intermediate variables? For example:

main = do
  -- Load sprites
  let sprites = SpriteCollection { 
      marioSprite <- loadBMP "assets/mario.bmp",
      goombaSprite <- loadBMP "assets/goomba.bmp",
      brickSprite <- loadBMP "assets/brick.bmp"
    }
  playIO (... sprites ...)

but that does not work.


Solution

  • Applicative is the tool for combining multiple values that are in the same sort of context.

    main = do
      sprites <- SpriteCollection <$> 
        loadBMP "mario" <*> 
        loadBMP "goomba" <*> 
        loadBMP "brick"
      ...
    

    In general,

    do
      x <- mx
      y <- my
      return (f x y)
    

    can be written as

    f <$> mx <*> my
    

    but only if the final call to f is the only thing that references x and y. This approach scales to arbitrarily many arguments. For small numbers of arguments, you can try something from the liftA family of functions, such as liftA2 f mx my.