Самым простым решением было бы вложить монады друг в друга, например, вот так:
-- 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
как Луна Goose предложено. Например, вот так:
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’