Reputation: 343
I have a Haskell datatype
data Document where
DocumentKind1 :: {
head :: Head
body :: Body
} -> Document
DocumentKind2 :: {
head :: Head
body :: Body
} -> Document
data Head where
HeadKind1 {
head_fields :: Int
...
} -> Head
HeadKind2 {
head_fields :: Int
...
} -> Head
data Body where
BodyKind1 {
body_fields :: Int
...
} -> Body
BodyKind2a {
body_fields :: Int
...
} -> Body
BodyKind2b {
body_fields :: Int
...
} -> Body
which by convention, as Haskell does not allow constraining data types, to follow the laws:
Document
is DocumentKind1
, then both Head
and Body
had to be of kind 1, id est, that they must be of HeadKind1
and BodyKind1
respectively.Document
is DocumentKind2
, then both Head
and Body
had to be of kind 2, id est, that they must be of HeadKind2
and BodyKind2a
or HeadKind2
and BodyKind2b
respectively.Likewise I also had a function, which takes Document
but only works when document is of Kind1
parseKind1 :: Parser Char Document
and want to test it with QuickCheck, but just defining Arbitrary
to make the Documents and later decoding and comparing, would feed the function Documents that are not valid.
Is there a way to restrict Arbritary to the respective constrained data types and if so, there is a way to dedicate test for every constrained subset of the data type?
Upvotes: 0
Views: 75
Reputation: 111
The other answer is good, but it's not always easy/possible to rewrite type hierarchies to their Head
s and Body
s.
When in this situation, a cheap and easy way to take a provided Arbitrary
instance and obtain a "filtered" version of it (i.e. so that each generated example satisfies desired conditions) is to use the function suchThat
:
suchThat :: Gen a -> (a -> Bool) -> Gen a
For example:
newtype Kind1Doc = Kind1Doc Document
instance Arbitrary Kind1Doc
where
arbitrary = fmap Kind1Doc $ arbitrary @Document `suchThat` \case
{ DocumentKind1
{ head = HeadKind1 {}
, body = BodyKind1 {}
} ->
True
; _ ->
False
}
Possible reasons to avoid: inefficient at runtime, namespace pollution.
Upvotes: 1
Reputation: 153172
Your constraints are particularly simple. To me it seems very reasonable to enforce them at the type level.
data Document = Document1 Head1 Body1 | Document2 Head2 Body2
data Head1 = Head1 HeadBoth ...
data Head2 = Head2 HeadBoth ...
data HeadBoth = HeadBoth { head_fields :: Int }
data Body1 = Body1 BodyBoth ...
data Body2 = Body2 BodyBoth Body2Subkind
data BodyBoth = BodyBoth { body_fields :: Int }
data Body2Subkind = Body2a ... | Body2b ...
Upvotes: 2