11# generic-arbitrary
22
3- [ ![ Build Status ] ( https://travis-ci.org /typeable/generic-arbitrary.svg?branch=master )] ( https://travis-ci.org /typeable/generic-arbitrary )
3+ [ ![ GitHub CI ] ( https://github.com /typeable/generic-arbitrary/workflows/haskell-ci/badge .svg )] ( https://github.com /typeable/generic-arbitrary/actions )
44
5- Deriving ` Arbitrary ` via ` Generic ` .
5+ # What?
6+
7+ Package for deriving ` Arbitrary ` via ` Generic ` .
68
79``` haskell
810import GHC.Generics (Generic )
911import Test.QuickCheck
1012import Test.QuickCheck.Arbitrary.Generic
1113
14+ data Expr
15+ = Lit Int
16+ | Add Expr Expr
17+ | Mul Expr Expr
18+ deriving (Eq , Show , Generic )
19+ deriving Arbitrary via (GenericArbitrary Expr )
20+ ```
21+
22+ Older versions of this package had a problem with hanging `arbitrary`
23+ method. Since `1.0 . 0 ` this problem almost solved.
24+
25+ For `QuickCheck ` older than `2.14 . 0 ` the `GenericArbitrary ` is not available, so
26+ you will need to write instances more verbosely
27+
28+ ``` haskell
1229data Expr
1330 = Lit Int
1431 | Add Expr Expr
@@ -20,19 +37,101 @@ instance Arbitrary Expr where
2037 shrink = genericShrink
2138```
2239
23- if type has an argument then ` Arg ` is required
40+ Which is generally the same.
41+
42+ # Infinite terms problem
43+
44+ The ` generic-arbitrary ` can partially handle the problem with recursive
45+ types. Assume the type ` R `
2446
2547``` haskell
26- data Expr lit
27- = Lit lit
28- | Add Expr Expr
29- | Mul Expr Expr
30- deriving (Eq , Show , Generic )
48+ data R = R R
49+ deriving Generic
50+ ```
51+
52+ there is no instance
53+
54+ ``` haskell
55+ instance Arbitrary R where
56+ arbitrary = genericArbitrary
57+ shrink = genericShrink
58+ ```
59+
60+ If you try to compile this you will get a type level error
61+
62+ > • R refers to itself in all constructors
3163
32- instance
33- ( Arbitrary lit
34- , Arg (Expr lit ) lit
35- ) => Arbitrary (Expr lit ) where
64+ Which means that there is no finite term for ` R ` because it is recursive in all
65+ it's constructors. But, if you correct the definition of ` R ` like this.
66+
67+ ``` haskell
68+ data R = R R | F
69+ deriving Generic
70+ ```
71+
72+ Then it will compile. And the `arbitrary` generated will not hang forever,
73+ because it respects the `size` parameter.
74+
75+ ## Limitation
76+
77+ There is a limitation of recursion detection:
78+
79+ ``` haskell
80+ data R1 = R1 R2
81+ deriving (Eq , Ord , Show , Generic )
82+ deriving anyclass NFData
83+ deriving Arbitrary via (GenericArbitrary R1 )
84+
85+ data R2 = R2 R1
86+ deriving (Eq , Ord , Show , Generic )
87+ deriving anyclass NFData
88+ deriving Arbitrary via (GenericArbitrary R2 )
89+ ```
90+
91+ This code will compile and the `arbitrary` generated will always hang. Yes ,
92+ there is a problem with mutually recursive types.
93+
94+ # Type parameters
95+
96+ Now lets see an example of datatype with parameters
97+
98+ ``` haskell
99+ data A a = A a
100+ deriving (Eq , Ord , Show )
101+ deriving anyclass NFData
102+ deriving (Generic )
103+
104+ instance (Arbitrary a ) => Arbitrary (A a ) where
105+ arbitrary = genericArbitrary
106+ shrink = genericShrink
107+ ```
108+
109+ It should work from first glance, but when compile it will throw an error:
110+
111+ > • Could not deduce (Test.QuickCheck.Arbitrary.Generic.GArbitrary
112+ > (A a)
113+ > (GHC.Generics.D1
114+ > ('GHC.Generics.MetaData "A" "ParametersTest" "main" 'False)
115+ > (GHC.Generics.C1
116+ > ('GHC.Generics.MetaCons "A" 'GHC.Generics.PrefixI 'False)
117+ > (GHC.Generics.S1
118+ > ('GHC.Generics.MetaSel
119+ > 'Nothing
120+ > 'GHC.Generics.NoSourceUnpackedness
121+ > 'GHC.Generics.NoSourceStrictness
122+ > 'GHC.Generics.DecidedLazy)
123+ > (GHC.Generics.Rec0 a))))
124+ > (TypesDiffer (A a) a))
125+ > arising from a use of ‘genericArbitrary’
126+
127+ Here the ` TypesDiffer ` is a type familty dealing with recursive types and
128+ helping us to eliminate inproper instances. To convince the compiller, that the
129+ ` a ` parameter is not an ` A a ` we must fix the instance with additional constraint
130+
131+ ``` haskell
132+ instance (Arg (A a ) a , Arbitrary a ) => Arbitrary (A a ) where
36133 arbitrary = genericArbitrary
37134 shrink = genericShrink
38135```
136+
137+ Now everything compiles and works as expected.
0 commit comments