Question:
How do I give an "IO SDL.Surface" to a function that expects an "SDL.Surface"?
I'd rather rethink my entire approach than resort to using something like "unsafePerformIO", unless this is actually the correct time to use it (which I doubt).
Further info:
I had a file filled with numbers and filepaths and I've parsed this file and loaded the images located at these paths into a list [(Int, IO SDL.Surface)]. Problem is, that the SDL.blitSurface function expects a normal SDL.Surface.
Error message:
Couldn't match type `IO SDL.Surface'
with `GHC.ForeignPtr.ForeignPtr SDL.SurfaceStruct'
Expected type: SDL.Surface
Actual type: IO SDL.Surface
I'm not sure that source code is necessary to answer the question, but I'll provide some anyway just in case it helps:
To load an image file I use:
loadImage :: FilePath -> IO SDL.Surface
loadImage [] = error "empty list"
loadImage a =
SDL.loadBMP a
To create the list of numbers and images I use:
createIDImageList :: [Tiletype] -> [(Int, IO SDL.Surface)]
createIDImageList a =
if null a then []
else [(tiletypeid $ a !! 0, loadImage (C8.unpack ( tiletypeimage ( a !! 0))))] ++ createIDImageList (tail a)
To retrieve the correct picture from this list, I use this function:
imageFromID :: Int -> [(Int, IO SDL.Surface)] -> Maybe (IO SDL.Surface)
imageFromID a b =
if null b then Nothing
else if a == (fst $ b !! 0) then Just (snd $ b !! 0)
else imageFromID a (tail b)
And finally I use the imageFromID with the SDL.blitSurface to draw the image, except that I can't due to IO.
Any time you end up with [IO Foobar]
, what you probably want is actually IO [Foobar]
. The sequence
function transforms one into the other. Or you can use mapM
instead of map
when creating the list in the first place.
In your example, it's a little more complicated, since we have [(Int, IO Surface)]
. Let me see what I can suggest...
loadImage
is an I/O action. It takes a filename and returns an IO action to load the image. Your createIDImageList
function is really
createIDImageList = map f
where
f a = (tiletypeid a, loadImage (C8.unpack ( tiletypeimage a) ) )
What you probably want to do is change f
to have type IO (Int, Surface)
rather than (Int, IO Surface)
. And then you can mapM f
, yielding a single I/O action that returns a list of stuff.
createIDImageList :: [Tiletype] -> IO [(Int, SDL.Surface)]
createIDImageList = mapM f
where
f a = do
surface <- loadImage (C8.unpack (tiletypeimage a) )
return (tiletypeid a, surface)
Regarding imageFromID
: what you probably want to do is something like this:
main = do
images <- createIDImageList (...)
...
let image5 = imageFromID 5 images
SDL.blitSurface image5 ...
The type of imageFromID
then becomes
imageFromID :: `Int -> [(Int, SDL.Surface)] -> Maybe SDL.Surface
(Since images
now has type [(Int, SDL.Surface)]
, with no IO
in it, thanks to <-
.)
What you're doing here is that createIDImageList
is actually loading everything off disk, and then you can use imageFromID
(which has no I/O in it) whenever you want to get the surface you're interested in.