Before doing stylistic refactoring, let's take a step back and think about the semantics of what your code is doing here.
You've got an IO
action that produces something of type Either String PNG.PNGImage
, where the Left
case is an error message. You think want to do something with the Right
case when it exists, while leaving the error message as is. Think about what this composite operation might look like, if you condensed it into a single, generalized combinator:
doIOWithError :: IO (Either String a) -> (a -> IO b) -> IO (Either String b)
doIOWithError x f = do x' <- x
case x' of
Left err -> return (Left err)
Right y -> f y
While that could be useful as is, you may already have noticed that its type signature looks suspiciously similar to (>>=) :: (Monad m) => m a -> (a -> m b) -> m b
. In fact, if we generalize one step further by allowing the function to produce errors as well, we have exactly the type of (>>=)
where m a
becomes IO (Either String a)
. Unfortunately, you can't make that a Monad
instance, because you can't just glue type constructors together directly.
What you can do is wrap it in a newtype alias, and in fact it turns out that someone already has: this is just Either
used as a monad transformer, so we want ErrorT String IO
. Rewriting your function to use that gives this:
loadTexture :: String -> ErrorT String IO GL.GLuint
loadTexture filename = do
p <- ErrorT $ loadPNGFile filename
lift $ oglLoadImg p
where
oglLoadImg :: PNG.PNGImage -> IO GL.GLuint
oglLoadImg png = do putStrLn "...I need todo IO stuff in here"
return 0
Now that we've merged the conceptual composite operation, we can begin condensing the specific operations more effectively. Collapsing the do
block into monadic function application is a good start:
loadTexture :: String -> ErrorT String IO GL.GLuint
loadTexture filename = lift . oglLoadImg =<< ErrorT (loadPNGFile filename)
where
oglLoadImg :: PNG.PNGImage -> IO GL.GLuint
oglLoadImg png = do putStrLn "...I need todo IO stuff in here"
return 0
And depending on what you're doing in oglLoadImg
, you might be able to do more.