在编写一个简单的QuickCheck URL生成器时,出现了嵌套单体的问题。

问题描述 投票:2回答:1

又是一个新手问题,可能是我没有掌握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片段的解包和包装的直觉是错误的。我哪里出错了?正确的直觉是什么?

非常感谢大家的帮助!

haskell uri monads quickcheck do-notation
1个回答
1
投票

最简单的解决方法是将单元嵌套在一起,比如像这样。

-- 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,你会得到错误的抱怨。

你可以检查一个类型实现了哪些实例,以及哪些类型实现了一个类型类,可以使用 :ighci:

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’
© www.soinside.com 2019 - 2024. All rights reserved.