编写简单的 QuickCheck URL 生成器时嵌套单子的问题
Problem with nested monads while writing a simple QuickCheck URL generator
另一个新手问题,可能是因为我没有掌握 Haskell 中的 Monadic do
:我想使用 Text.URI
为格式良好的 URI 编写一个简单的 QuickCheck 生成器从 modern-uri
包中输入。据我了解,这里涉及两种类型的 monad:MonadThrow
用于 URI 构造时的错误处理,以及来自 QuickCheck 的 Gen
。
这是我尝试实现生成器的尝试。它不进行类型检查:
import qualified Text.URI as URI
uriGen :: Gen URI.URI
uriGen = do
sc <- elements ["https", "http", "ftps", "ftp"]
tld <- elements [".com", ".org", ".edu"]
hostName <- nonEmptySafeTextGen -- (:: Gen Text), a simple generator for printable text.
uri <- do
scheme <- URI.mkScheme sc
host <- URI.mkHost $ (hostName <> "." <> tld)
return $ URI.URI (Just scheme) (Right (URI.Authority Nothing host Nothing)) Nothing [] Nothing
return uri
我的理解是,外部 do
块属于 Gen
monad,而内部块处理 MonadThrow
。我尝试从 Gen
中解开 Text
部分,然后使用解开的 Text
构建 URI
部分,从 MonadThrow
中解开它们,然后重新组装整个 URI,最后将其包装在一个新的 Gen
.
中
但是,我收到以下类型检查错误:
• No instance for (MonadThrow Gen)
arising from a use of ‘URI.mkScheme’
• In a stmt of a 'do' block: scheme <- URI.mkScheme sc
In a stmt of a 'do' block:
uri <- do scheme <- URI.mkScheme sc
host <- URI.mkHost $ (hostName <> "." <> tld)
return
$ URI.URI
(Just scheme)
(Right (URI.Authority Nothing host Nothing))
Nothing
[]
Nothing
从错误来看,我怀疑我对展开和包装 URI 片段的直觉是错误的。我哪里错了?什么是正确的直觉?
非常感谢您的帮助!
最简单的解决方案是将 monad 彼此嵌套,例如:
-- One instance for MonadThrow is Maybe, so this is a possible type signature
-- uriGen :: Gen (Maybe URI.URI)
uriGen :: MonadThrow m => Gen (m URI.URI)
uriGen = do
sc <- elements ["https", "http", "ftps", "ftp"]
tld <- elements [".com", ".org", ".edu"]
hostName <- nonEmptySafeTextGen -- (:: Gen Text), a simple generator for printable text.
let uri = do
scheme <- URI.mkScheme sc
host <- URI.mkHost $ (hostName <> "." <> tld)
return $ URI.URI
{ uriScheme = Just scheme
, uriAuthority = Right (URI.Authority Nothing host Nothing)
, uriPath = Nothing
, uriQuery = []
, uriFragment = Nothing
}
return uri
现在 uri
变量被解释为相对于 Gen
monad 的纯值,并且 MonadThrow
将作为单独的层包装在其中。
如果你想让它重试直到成功,你可以按照moonGoose的建议使用suchThatMap
。例如像这样:
uriGen' :: Gen URI.URI
uriGen' = suchThatMap uriGen id
这是有效的,因为 suchThatMap
有类型
suchThatMap :: Gen a -> (a -> Maybe b) -> Gen b
所以当你给它恒等函数作为第二个参数时,它就变成了
\x -> suchThatMap x id :: Gen (Maybe b) -> Gen b
与上面的类型匹配:uriGen :: Gen (Maybe URI.URI)
.
编辑: 在评论中回答您的问题:
MonadThrow
是 class 类型,它是 Monad
的超 class(参见 documentation)。你写的相当于
uriGen :: Gen URI.URI
uriGen = do
sc <- elements ["https", "http", "ftps", "ftp"]
tld <- elements [".com", ".org", ".edu"]
hostName <- nonEmptySafeTextGen
scheme <- URI.mkScheme sc
host <- URI.mkHost $ (hostName <> "." <> tld)
URI.URI (Just scheme) (Right (URI.Authority Nothing host Nothing)) Nothing [] Nothing
换句话说,do 的嵌套没有任何效果,它试图解释 Gen
monad 中的所有内容。由于 Gen
不在 list of instances for MonadThrow
中,您会收到有关该错误的抱怨。
您可以在 ghci
:
中使用 :i
检查一个类型实现了哪些实例以及哪些类型实现了一个类型 class
Prelude Test.QuickCheck> :i Gen
newtype Gen a
= Test.QuickCheck.Gen.MkGen {Test.QuickCheck.Gen.unGen :: Test.QuickCheck.Random.QCGen
-> Int -> a}
-- Defined in ‘Test.QuickCheck.Gen’
instance [safe] Applicative Gen -- Defined in ‘Test.QuickCheck.Gen’
instance [safe] Functor Gen -- Defined in ‘Test.QuickCheck.Gen’
instance [safe] Monad Gen -- Defined in ‘Test.QuickCheck.Gen’
instance [safe] Testable prop => Testable (Gen prop)
-- Defined in ‘Test.QuickCheck.Property’
另一个新手问题,可能是因为我没有掌握 Haskell 中的 Monadic do
:我想使用 Text.URI
为格式良好的 URI 编写一个简单的 QuickCheck 生成器从 modern-uri
包中输入。据我了解,这里涉及两种类型的 monad:MonadThrow
用于 URI 构造时的错误处理,以及来自 QuickCheck 的 Gen
。
这是我尝试实现生成器的尝试。它不进行类型检查:
import qualified Text.URI as URI
uriGen :: Gen URI.URI
uriGen = do
sc <- elements ["https", "http", "ftps", "ftp"]
tld <- elements [".com", ".org", ".edu"]
hostName <- nonEmptySafeTextGen -- (:: Gen Text), a simple generator for printable text.
uri <- do
scheme <- URI.mkScheme sc
host <- URI.mkHost $ (hostName <> "." <> tld)
return $ URI.URI (Just scheme) (Right (URI.Authority Nothing host Nothing)) Nothing [] Nothing
return uri
我的理解是,外部 do
块属于 Gen
monad,而内部块处理 MonadThrow
。我尝试从 Gen
中解开 Text
部分,然后使用解开的 Text
构建 URI
部分,从 MonadThrow
中解开它们,然后重新组装整个 URI,最后将其包装在一个新的 Gen
.
但是,我收到以下类型检查错误:
• No instance for (MonadThrow Gen)
arising from a use of ‘URI.mkScheme’
• In a stmt of a 'do' block: scheme <- URI.mkScheme sc
In a stmt of a 'do' block:
uri <- do scheme <- URI.mkScheme sc
host <- URI.mkHost $ (hostName <> "." <> tld)
return
$ URI.URI
(Just scheme)
(Right (URI.Authority Nothing host Nothing))
Nothing
[]
Nothing
从错误来看,我怀疑我对展开和包装 URI 片段的直觉是错误的。我哪里错了?什么是正确的直觉?
非常感谢您的帮助!
最简单的解决方案是将 monad 彼此嵌套,例如:
-- One instance for MonadThrow is Maybe, so this is a possible type signature
-- uriGen :: Gen (Maybe URI.URI)
uriGen :: MonadThrow m => Gen (m URI.URI)
uriGen = do
sc <- elements ["https", "http", "ftps", "ftp"]
tld <- elements [".com", ".org", ".edu"]
hostName <- nonEmptySafeTextGen -- (:: Gen Text), a simple generator for printable text.
let uri = do
scheme <- URI.mkScheme sc
host <- URI.mkHost $ (hostName <> "." <> tld)
return $ URI.URI
{ uriScheme = Just scheme
, uriAuthority = Right (URI.Authority Nothing host Nothing)
, uriPath = Nothing
, uriQuery = []
, uriFragment = Nothing
}
return uri
现在 uri
变量被解释为相对于 Gen
monad 的纯值,并且 MonadThrow
将作为单独的层包装在其中。
如果你想让它重试直到成功,你可以按照moonGoose的建议使用suchThatMap
。例如像这样:
uriGen' :: Gen URI.URI
uriGen' = suchThatMap uriGen id
这是有效的,因为 suchThatMap
有类型
suchThatMap :: Gen a -> (a -> Maybe b) -> Gen b
所以当你给它恒等函数作为第二个参数时,它就变成了
\x -> suchThatMap x id :: Gen (Maybe b) -> Gen b
与上面的类型匹配:uriGen :: Gen (Maybe URI.URI)
.
编辑: 在评论中回答您的问题:
MonadThrow
是 class 类型,它是 Monad
的超 class(参见 documentation)。你写的相当于
uriGen :: Gen URI.URI
uriGen = do
sc <- elements ["https", "http", "ftps", "ftp"]
tld <- elements [".com", ".org", ".edu"]
hostName <- nonEmptySafeTextGen
scheme <- URI.mkScheme sc
host <- URI.mkHost $ (hostName <> "." <> tld)
URI.URI (Just scheme) (Right (URI.Authority Nothing host Nothing)) Nothing [] Nothing
换句话说,do 的嵌套没有任何效果,它试图解释 Gen
monad 中的所有内容。由于 Gen
不在 list of instances for MonadThrow
中,您会收到有关该错误的抱怨。
您可以在 ghci
:
:i
检查一个类型实现了哪些实例以及哪些类型实现了一个类型 class
Prelude Test.QuickCheck> :i Gen
newtype Gen a
= Test.QuickCheck.Gen.MkGen {Test.QuickCheck.Gen.unGen :: Test.QuickCheck.Random.QCGen
-> Int -> a}
-- Defined in ‘Test.QuickCheck.Gen’
instance [safe] Applicative Gen -- Defined in ‘Test.QuickCheck.Gen’
instance [safe] Functor Gen -- Defined in ‘Test.QuickCheck.Gen’
instance [safe] Monad Gen -- Defined in ‘Test.QuickCheck.Gen’
instance [safe] Testable prop => Testable (Gen prop)
-- Defined in ‘Test.QuickCheck.Property’