又是一个新手问题,可能是我没有掌握Monadic的缘故。do
在Haskell中,我想写一个简单的QuickCheck生成器。我想为格式良好的URI写一个简单的QuickCheck生成器,使用的是 Text.URI
类型从 modern-uri
包。根据我的理解,这里涉及到两种类型的单片机。MonadThrow
用于处理URI构造时的错误, 和 Gen
从QuickCheck。
这是我实现生成器的尝试。它没有进行类型检查。
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
单体,而内部的单体则处理 MonadThrow
. 我试图拆开 Text
从他们 Gen
然后使用未包装的 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片段的解包和包装的直觉是错误的。我哪里出错了?正确的直觉是什么?
非常感谢大家的帮助!
最简单的解决方法是将单元嵌套在一起,比如像这样。
-- 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
单元和 MonadThrow
将作为一个单独的层包裹在它里面。
如果你想让它重试直到成功,你可以使用 suchThatMap
正如moonGoose建议的那样。比如像这样。
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)
.
EDIT: 为了回答你在评论中的问题:
MonadThrow
是一个类型类,它是一个超类... Monad
(见 文件). 你写的相当于
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
单项式。由于 Gen
不在 实例列表 MonadThrow
,你会得到错误的抱怨。
你可以检查一个类型实现了哪些实例,以及哪些类型实现了一个类型类,可以使用 :i
在 ghci
:
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’