Vor dem Durchführen von stilistischem Refactoring sollten wir einen Schritt zurücktreten und über die Semantik nachdenken, was dein Code hier tut.
Du hast eine IO
-Aktion, die etwas vom Typ Either String PNG.PNGImage
produziert, wobei der Left
-Fall eine Fehlermeldung ist. Du möchtest etwas mit dem Right
-Fall machen, wenn er existiert, während die Fehlermeldung erhalten bleibt. Überlege, wie diese zusammengesetzte Operation aussehen könnte, wenn du sie in einen einzigen, verallgemeinerten Kombinator zusammenfassen würdest:
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
Obwohl das so nützlich sein könnte, hast du vielleicht bereits festgestellt, dass die Typsignatur verdächtig ähnlich aussieht wie (>>=) :: (Monad m) => m a -> (a -> m b) -> m b
. Tatsächlich haben wir, wenn wir einen Schritt weiter verallgemeinern, indem wir die Funktion auch Fehler produzieren lassen, genau den Typ von (>>=)
, bei dem m a
zu IO (Either String a)
Monad-Instanz machen, weil du Typkonstruktoren nicht direkt zusammenkleben kannst.
Was du machen kannst, ist es in ein neues Typalias zu verpacken, und tatsächlich hat das schon jemand getan: das ist einfach Either
als Monaden-Transformer verwendet, also wollen wir ErrorT String IO
. Wenn du deine Funktion so umschreibst, erhältst du das hier:
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 "...Hier muss ich IO-Zeug machen"
return 0
Nachdem wir die konzeptuelle zusammengesetzte Operation zusammengeführt haben, können wir beginnen, die spezifischen Operationen effektiver zu kondensieren. Das Zusammenführen des do
-Blocks in monadische Funktionsanwendung ist ein guter Anfang:
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 "...Hier muss ich noch IO-Zeug machen"
return 0
Und je nachdem, was du in oglLoadImg
machst, könntest du noch mehr tun.