电梯在 MT 堆栈中的组成

composition of lift's in MT stack

我很难理解这个编译错误。代码如下:

#!/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 坐在底部来构建这样的堆栈,所以我猜这是不好的做法? "design pattern"这里有什么好?我知道 "design patter" 这个词在 haskell 中不存在,但我想不出更好的词。非常感谢!

我首先减少了 monad t运行sformer 栈中的层数来缩小你的 代码:

    #!/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

它的 return 类型是 ConduitM i o m r 其中 m 必须是 MonadResource.

现在让我们再看一下 readHost 的最终 return 类型。 Cmd () BS.ByteString () 只不过是 ConduitM () BS.ByteString IO () 伪装。

比较 Conduit () BS.ByteString IO ()ConduitM i o m r 可以看出 m 对应 IO.

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

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

    -- 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

这编译没有错误,也打印到标准输出。

我相信您可以将此修复程序用于较大的程序。至少它解释了错误。