Reputation: 1906
Another newbie question that probably results from me not having grasped Monadic do
in Haskell: I want to write a simple QuickCheck generator for well-formed URIs, using the Text.URI
type from the modern-uri
package. To my understanding, there are two types of monads involved here: MonadThrow
for error handling upon URI construction, and Gen
from QuickCheck.
Here is my attempt to implement the generator. It does not type check:
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
My understanding is that the outer do
block pertains to the Gen
monad while the inner one handles MonadThrow
. I attempt to unwrap the Text
pieces from their Gen
and then use the unwrapped Text
to build up URI
pieces, unwrap them from their MonadThrow
, then reassemble the entire URI, and finally wrap it in a new Gen
.
However, I get the following type-check error:
• 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
From the error, I suspect that my intuition about unwrapping and wrapping the URI pieces is wrong. Where do I err? What would be the right intuition?
Thanks very much for your help!
Upvotes: 2
Views: 124
Reputation: 2615
The easiest solution would be to nest the monads within each other, for example like this:
-- 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
Now the uri
variable is interpreted as a pure value with respect to the Gen
monad and the MonadThrow
will be wrapped as a separate layer inside it.
If you want it to retry until it succeeds, you can use suchThatMap
as moonGoose suggested. For example like this:
uriGen' :: Gen URI.URI
uriGen' = suchThatMap uriGen id
This works because suchThatMap
has type
suchThatMap :: Gen a -> (a -> Maybe b) -> Gen b
so when you give it the identity function as a second argument, it becomes
\x -> suchThatMap x id :: Gen (Maybe b) -> Gen b
which matches the type above: uriGen :: Gen (Maybe URI.URI)
.
EDIT: To answer your question in the comments:
MonadThrow
is a typeclass that is a superclass of Monad
(see documentation). What you wrote is equivalent to
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
In other words, the nesting of do has no effect and it tries to interpret everything in the Gen
monad. Since Gen
is not in the list of instances for MonadThrow
, you get the error complaining about that.
You can check which instances a type implements and which types implements a type class using :i
in 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’
Upvotes: 1