如何从 do 表示法传递到 (>>=) 运算符表示法?

How to pass from a do notation to a (>>=) operator notation?

我真的不明白为什么这样的代码行真的有效:

来自Network.HTTP.Simple

{-# LANGUAGE OverloadedStrings #-}
import Network.HTTP.Simple
import qualified Data.ByteString.Char8 as B8

main :: IO ()
main = httpBS "http://example.com" >>= B8.putStrLn . getResponseBody

我可以用 do 符号重写这些东西:

test = do
          request <- return "http://example.com"
          result <- httpBS request
          let body = getResponseBody result
          B8.putStrLn body

即使我无法弄清楚 return "http://example.com" 的类型,这仍然有效。

问题 1:编译器如何找到我要使用的 Monad?

我的猜测是:它来自于 do 块的 return 是一个 IO(),所以它是一个 IO(请求)?

现在,当尝试在更复杂的代码中使用 httpBS 时,我遇到了一些困难

test.hs 文件:

{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedStrings #-}

import Network.HTTP.Simple
import qualified Data.ByteString.Char8 as B8


request = parseRequest "http://example.com" 

这给出了错误:

Prelude> :load test.hs
[1 of 1] Compiling Main             ( test.hs, interpreted )

test.hs:8:11: error:
    * Ambiguous type variable `m0' arising from a use of `parseRequest'
      prevents the constraint `(Control.Monad.Catch.MonadThrow
                                  m0)' from being solved.
      Relevant bindings include
        request :: m0 Request (bound at test.hs:8:1)
      Probable fix: use a type annotation to specify what `m0' should be.
      These potential instances exist:
        instance e ~ GHC.Exception.SomeException =>
                 Control.Monad.Catch.MonadThrow (Either e)
          -- Defined in `Control.Monad.Catch'
        instance Control.Monad.Catch.MonadThrow IO
          -- Defined in `Control.Monad.Catch'
        instance Control.Monad.Catch.MonadThrow Maybe
          -- Defined in `Control.Monad.Catch'
        ...plus one other
        ...plus 15 instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    * In the expression: parseRequest "http://example.com"
      In an equation for `request':
          request = parseRequest "http://example.com"
  |
8 | request = parseRequest "http://example.com"
  |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Failed, no modules loaded. 

好的。在解释器中输入相同的内容:

*Main Network.HTTP.Simple> import Network.HTTP.Simple
*Main Network.HTTP.Simple> req = parseRequest "http://example.com"
*Main Network.HTTP.Simple> :t req
req :: Control.Monad.Catch.MonadThrow m => m Request
*Main Network.HTTP.Simple> req
Request {
  host                 = "example.com"
  port                 = 80
  secure               = False
  requestHeaders       = []
  path                 = "/"
  queryString          = ""
  method               = "GET"
  proxy                = Nothing
  rawBody              = False
  redirectCount        = 10
  responseTimeout      = ResponseTimeoutDefault
  requestVersion       = HTTP/1.1
}

它看起来像我已经偶然发现的可怕的单态限制内容

所以我知道我必须给 type.It 确定,但后来我不知道如何用 >>= 符号来使用它,我只能设法让它与 do 一起工作表示法:

-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedStrings #-}

import Network.HTTP.Simple
import qualified Data.ByteString.Char8 as B8

url  = "http://example.com"
maybeRequest :: Maybe Request
maybeRequest = parseRequest url 

ioRequest :: IO Request
ioRequest = parseRequest url

--no. wrong type ioRequest.
--testKO = httpBS ioRequest >>= B8.putStrLn . getResponseBody

--How to have it working with a one-liner and  the >>= notation ? 

--do notation ok
test = do 
        request <- ioRequest
        response <- httpBS request
        let body = getResponseBody response
        B8.putStrLn body

Q2:如果Request是先构建的,如何使用Request和httpBS加上(>>=)操作符?

Q1: do-block 的每一行必须是 "in" 相同的 monad。 (这就是 maybeRequest 不起作用的原因;您不能将 Maybe monad 与 IO monad 混合使用。)

Q2:接近,但不完全。

ioRequest 是一个 I/O 动作,因此您需要使用 >>= 从中提取请求:

test = ioRequest >>= httpBS >>= B8.putStrLn . getResponseBody

虽然我在这里,但我还要指出

request <- return "http://example.com/"

完全等同于

let request = "http://example.com/"

I cannot figure out what is the type of return "http://example.com"

幸运的是,GHCi 可以:

> :t return "http://example.com"
return "http://example.com" :: Monad m => m [Char]

因此对于 returns 一个字符串的任何 monad 来说,它都是一个有效的 monad 动作。

因为 return 是 Monad 类型 class 定义的一部分,所以这不足为奇。

Q1 : How the compiler manage to find which Monad I want to use ?

它使用类型推断。默认情况下,在 do 块内将其限制为 monad,return "..." 也是如此。 (RebindableSyntax 允许您将 do 块的隐式 (>>=) 重载给任何运算符,但这不是您经常看到的东西。)

动作 httpBS :: MonadIO m => Request -> m (Response ByteString) 进一步将其限制为称为 MonadIO m 的东西,这显然是一种具有 liftIO :: Monad m => IO a -> m a 的特殊类型的 Monad。 MonadIO m 的最简单示例是 IO,但编译器尚未将其具体化。

最后,B8.putStrLn :: ByteString -> IO () 约束 test :: IO ()

Q2 : how to use the Request and httpBS with the (>>=) operator if the Request is to be build previously ? How to have it working with a one-liner and the >>= notation ?

do-blocks 和 point-free one-liner 都可以很方便,也有点神奇。您可以通过对 do 块进行去糖处理来桥接这两个符号,然后执行 η-reduction. A good read for understanding do-notation is still Philip Wadler's Monads for functional programming.

以下:

test = do
  request <- ioRequest
  response <- httpBS request
  B8.putStrLn (getResponseBody response)

脱糖成:

test =
  ioRequest >>= \request ->
  httpBS request >>= \response ->
  B8.putStrLn (getResponseBody response)

但是B8.putStrLn (getResponseBody response)
(B8.putStrLn . getResponseBody) response.

(此转换的要点是将两个 lambda 表达式表示为 \x -> f x。)

所以这变成了:

test =
  (ioRequest >>= \request -> httpBS request)
    >>= \response -> (B8.putStrLn . getResponseBody) response
  • 但是\request -> httpBS request只是httpBS

  • \response -> (B8.putStrLn . getResponseBody) response
    只是 B8.putStrLn . getResponseBody.

所以这变成了:

test =
  (ioRequest >>= httpBS)
    >>= B8.putStrLn . getResponseBody

重新格式化以适合一行:

test = ioRequest >>= httpBS >>= B8.putStrLn . getResponseBody