git.iko.soy/typeable/generic-arbitrary
on master help sign in
140 lines 3.9 kB history raw
1# generic-arbitrary
3[![Haskell-CI](https://github.com/typeable/generic-arbitrary/actions/workflows/haskell-ci.yml/badge.svg)](https://github.com/typeable/generic-arbitrary/actions/workflows/haskell-ci.yml)
5# What?
7Package for deriving `Arbitrary` via `Generic`.
9``` haskell
10import GHC.Generics (Generic)
11import Test.QuickCheck
12import Test.QuickCheck.Arbitrary.Generic
14data Expr
15 = Lit Int
16 | Add Expr Expr
17 | Mul Expr Expr
18 deriving (Eq, Show, Generic)
19 deriving Arbitrary via (GenericArbitrary Expr)
20```
22Older versions of this package had a problem with hanging `arbitrary`
23method. Since `1.0.0` this problem almost solved.
25For `QuickCheck` older than `2.14.0` the `GenericArbitrary` is not available, so
26you will need to write instances more verbosely
28``` haskell
29data Expr
30 = Lit Int
31 | Add Expr Expr
32 | Mul Expr Expr
33 deriving (Eq, Show, Generic)
35instance Arbitrary Expr where
36 arbitrary = genericArbitrary
37 shrink = genericShrink
38```
40Which is generally the same.
42# Infinite terms problem
44The `generic-arbitrary` can partially handle the problem with recursive
45types. Assume the type `R`
47``` haskell
48data R = R R
49 deriving Generic
50```
52there is no instance
54``` haskell
55instance Arbitrary R where
56 arbitrary = genericArbitrary
57 shrink = genericShrink
58```
60If you try to compile this you will get a type level error
62> • R refers to itself in all constructors
64Which means that there is no finite term for `R` because it is recursive in all
65it's constructors. But, if you correct the definition of `R` like this.
67``` haskell
68data R = R R | F
69 deriving Generic
70```
72Then it will compile. And the `arbitrary` generated will not hang forever,
73because it respects the `size` parameter.
75## Limitation
77There is a limitation of recursion detection:
79``` haskell
80data R1 = R1 R2
81 deriving (Eq, Ord, Show, Generic)
82 deriving anyclass NFData
83 deriving Arbitrary via (GenericArbitrary R1)
85data R2 = R2 R1
86 deriving (Eq, Ord, Show, Generic)
87 deriving anyclass NFData
88 deriving Arbitrary via (GenericArbitrary R2)
89```
91This code will compile and the `arbitrary` generated will always hang. Yes,
92there is a problem with mutually recursive types.
94# Type parameters
96Now let's see an example of datatype with parameters
98``` haskell
99data A a = A a
100 deriving (Eq, Ord, Show)
101 deriving anyclass NFData
102 deriving (Generic)
104instance (Arbitrary a) => Arbitrary (A a) where
105 arbitrary = genericArbitrary
106 shrink = genericShrink
107```
109It should work from first glance, but when compile it will throw an error:
111```
112• Could not deduce (Test.QuickCheck.Arbitrary.Generic.GArbitrary
113 (A a)
114 (GHC.Generics.D1
115 ('GHC.Generics.MetaData "A" "ParametersTest" "main" 'False)
116 (GHC.Generics.C1
117 ('GHC.Generics.MetaCons "A" 'GHC.Generics.PrefixI 'False)
118 (GHC.Generics.S1
119 ('GHC.Generics.MetaSel
120 'Nothing
121 'GHC.Generics.NoSourceUnpackedness
122 'GHC.Generics.NoSourceStrictness
123 'GHC.Generics.DecidedLazy)
124 (GHC.Generics.Rec0 a))))
125 (TypesDiffer (A a) a))
126 arising from a use of ‘genericArbitrary’
127```
129Here the `TypesDiffer` is a type familty dealing with recursive types and
130helping us to eliminate inproper instances. To convince the compiller, that the
131`a` parameter is not an `A a` we must fix the instance with additional
132constraint `Arg (A a) a`
134``` haskell
135instance (Arg (A a) a, Arbitrary a) => Arbitrary (A a) where
136 arbitrary = genericArbitrary
137 shrink = genericShrink
138```
140Now everything compiles and works as expected.