我可以在这里使用bind/fmap吗?

6 haskell

loadTexture :: String -> IO (Either String GL.GLuint)
loadTexture filename = do
    p <- PNG.loadPNGFile filename
    oglLoadImg p
    where
        oglLoadImg :: (Either String PNG.PNGImage) -> IO (Either String GL.GLuint)
        oglLoadImg (Left e) = return $ Left e 
        oglLoadImg (Right png) = do
            ... I need todo IO stuff in here
Run Code Online (Sandbox Code Playgroud)

上面的代码看起来真的很臃肿和讨厌.我该怎么做才能让它变得更简单?

ham*_*mar 14

你基本上想要Either emonad和IOmonad 的组合.这就是monad变形金刚的用途!

在这种情况下,你可以使用ErrorT单子转换它增加了使用错误处理Either,以潜在的单子,在这种情况下IO.

import Control.Monad.Error

loadTexture :: String -> IO (Either String GL.GLuint)
loadTexture filename = runErrorT $ ErrorT (PNG.loadPNGFile filename) >>= oglLoadImg
    where
        oglLoadImg :: PNG.PNGImage -> ErrorT String IO GL.GLuint
        oglLoadImg png = do
            -- [...]
Run Code Online (Sandbox Code Playgroud)

这保留了旧的界面,虽然它可能更好地ErrorT用于你的功能,并runErrorT在你的main功能中调用.

loadTexture :: String -> ErrorT String IO GL.GLuint
loadTexture filename = ErrorT (PNG.loadPNGFile filename) >>= oglLoadImg
    where
        oglLoadImg :: PNG.PNGImage -> ErrorT String IO GL.GLuint
        oglLoadImg png = do
            -- [...]
Run Code Online (Sandbox Code Playgroud)

Monad变形金刚可能需要一些习惯,但它们非常有用.


C. *_*ann 12

在进行风格重构之前,让我们退后一步,考虑一下代码在这里所做的事情的语义.

你有一个IO产生类型的动作Either String PNG.PNGImage,其中Left大小写是一个错误信息.您认为想要在Right案例存在的情况下执行某些操作,同时保留错误消息.想想这个复合操作可能是什么样子,如果你把它压缩成一个通用的组合器:

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
Run Code Online (Sandbox Code Playgroud)

虽然这可能是有用的,但您可能已经注意到它的类型签名看起来很可疑(>>=) :: (Monad m) => m a -> (a -> m b) -> m b.事实上,如果我们概括了一步,允许产生错误以及功能,我们有确切的类型(>>=),其中m aIO (Either String a).不幸的是,你不能把它作为一个Monad实例,因为你不能直接将类型构造函数粘合在一起.

你可以做的是将它包装在一个newtype别名中,事实上事实证明有人已经拥有:这只是Either用作monad变换器,所以我们想要ErrorT String IO.重写你要使用的函数,这样就可以了:

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
Run Code Online (Sandbox Code Playgroud)

现在我们已经合并了概念复合操作,我们可以开始更有效地压缩特定操作.将do块折叠到monadic函数应用程序是一个好的开始:

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
Run Code Online (Sandbox Code Playgroud)

根据你所做的事情oglLoadImg,你可能会做得更多.