MT堆栈中电梯的组成

时间:2018-08-02 22:03:55

标签: haskell conduit

我在理解此编译错误时遇到很多麻烦。代码如下:

#!/usr/bin/env stack                                                                                                                                                              
-- stack script --resolver lts-8.22                                                                                                                                               
{-# LANGUAGE OverloadedStrings #-}                                                                                                                                                

import           Network.HTTP.Simple                                                                                                                                              
import qualified Data.ByteString       as BS                                                                                                                                      
import           Control.Monad.Reader                                                                                                                                             
import           Control.Monad.Except                                                                                                                                             
import           Control.Monad.Trans.Class                                                                                                                                        
import           Data.Conduit                                                                                                                                                     



type Cmd i o = ReaderT (BS.ByteString) (ExceptT HttpException (ConduitM i o IO))                                                                                                  

runCmd :: Cmd i o a -> BS.ByteString -> ConduitM i o IO (Either HttpException a)                                                                                                  
runCmd cmdTS host = runExceptT $ runReaderT cmdTS host                                                                                                                            


readHost :: Cmd () BS.ByteString ()                                                                                                                                               
readHost = do                                                                                                                                                                     
host <- ask                                                                                                                                                                     
let req = setRequestMethod "GET"                                                                                                                                                
        $ setRequestHost host                                                                                                                                                   
        $ defaultRequest                                                                                                                                                        
lift . lift $ httpSource req getResponseBody  

基本上,我有4层的MT堆栈,在readHost中,我试图将对象从第二层提升到底层,再到顶层,因此我将两个lift组合在一起。但是遗憾的是代码无法编译,并且出现以下错误:

[-Wdeferred-type-errors]                                                                                                                                                          
• No instance for (Control.Monad.Trans.Resource.Internal.MonadResource                                                                                                        
                     IO)                                                                                                                                                      
    arising from a use of ‘httpSource’                                                                                                                                        
• In the second argument of ‘($)’, namely                                                                                                                                     
    ‘httpSource req getResponseBody’                                                                                                                                          
  In a stmt of a 'do' block:                                                                                                                                                  
    lift . lift $ httpSource req getResponseBody                                                                                                                              
  In the expression:                                                                                                                                                          
    do host <- ask                                                                                                                                                            
       let req                                                                                                                                                                
             = setRequestMethod "GET" $ setRequestHost host $ defaultRequest                                                                                                  
       lift . lift $ httpSource req getResponseBody

请帮助我了解此错误消息的含义。顺便说一句,我还没有看到有人用ConduiT放在底部来构建这样的堆栈,所以我猜这是不好的做法吗?在这里什么是好的“设计模式”?我知道haskell中不存在“设计模式”一词,但我想不出更好的词。非常感谢!

1 个答案:

答案 0 :(得分:2)

我首先减少monad变压器堆栈中的层以缩小您的 代码:

    #!/usr/bin/env stack
    -- stack script --resolver lts-8.22
    {-# LANGUAGE OverloadedStrings #-}

    import           Network.HTTP.Simple
    import qualified Data.ByteString           as BS
    import           Control.Monad.Reader
    import           Control.Monad.Except
    import           Control.Monad.Trans.Class
    import           Data.Conduit

    type Cmd i o = ConduitM i o IO

    readHost :: BS.ByteString -> Cmd () BS.ByteString ()
    readHost host = do
      let req = setRequestMethod "GET" . setRequestHost host $ defaultRequest
      httpSource req getResponseBody

然后我运行了stack {scriptname},并得到了与您得到的错误非常相似的错误 对于较大的堆栈:

    {scriptname}:17:3: error:
        • No instance for (resourcet-1.1.9:Control.Monad.Trans.Resource.Internal.MonadResource
                             IO)
            arising from a use of ‘httpSource’
        • In a stmt of a 'do' block: httpSource req getResponseBody
          In the expression:
            do { let req
                       = setRequestMethod "GET" . setRequestHost host $ defaultRequest;
                 httpSource req getResponseBody }
          In an equation for ‘readHost’:
              readHost host
                = do { let req = ...;
                       httpSource req getResponseBody }

弄清楚这种较小的情况,很可能会使版本更复杂 更容易解决。

让我们看一下httpSource的类型:

    httpSource :: (MonadResource m, MonadIO n) =>
                     Request
                  -> (Response (ConduitM i ByteString n ()) -> ConduitM i o m r)
                  -> ConduitM i o m r

其返回类型为ConduitM i o m r,其中m必须是 MonadResource

现在让我们再次看一下readHost的最终返回类型。 Cmd () BS.ByteString ()就是什么 ConduitM () BS.ByteString IO ()变相。

比较Conduit () BS.ByteString IO ()ConduitM i o m r可以看到 mIO相对应。

IOMonadResource的实例吗?根据GHC的说法,不是。

使用Hoogle环顾四周,让我们知道ResourceT IOMonadResource的实例。让我们在程序中尝试一下。

    -- stack script --resolver lts-8.22
    {-# LANGUAGE OverloadedStrings #-}

    import           Network.HTTP.Simple
    import qualified Data.ByteString           as BS
    import           Control.Monad.Reader
    import           Control.Monad.Except
    import           Control.Monad.Trans.Class
    import           Control.Monad.Trans.Resource
    import           Data.Conduit

    main :: IO ()
    main = putStrLn "hello"

    type Cmd i o = ConduitM i o (ResourceT IO)

    readHost :: BS.ByteString -> Cmd () BS.ByteString ()
    readHost host = do
      let req = setRequestMethod "GET" . setRequestHost host $ defaultRequest
      httpSource req getResponseBody

这将正确编译,并打印到标准输出。

我相信您可以对较大的程序使用此修复程序。至少可以解释该错误。