git.iko.soy/typeable/generic-arbitrary
on master help sign in
more docs
fff49dcAleksey Uymanov
README.md+111−12
@@ -1,14 +1,31 @@
1# generic-arbitrary
1# generic-arbitrary
2
2
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)
4
4
5+# What?
6+
5−Deriving `Arbitrary` via `Generic`.
7+Package for deriving `Arbitrary` via `Generic`.
6
8
7``` haskell
9``` haskell
8import GHC.Generics (Generic)
10import GHC.Generics (Generic)
9import Test.QuickCheck
11import Test.QuickCheck
10import Test.QuickCheck.Arbitrary.Generic
12import Test.QuickCheck.Arbitrary.Generic
11
13
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
12data Expr
29data Expr
13 = Lit Int
30 = Lit Int
14 | Add Expr Expr
31 | Add Expr Expr
@@ -20,19 +37,101 @@ instance Arbitrary Expr where
20 shrink = genericShrink
37 shrink = genericShrink
21```
38```
22
39
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`
24
46
25``` haskell
47``` haskell
26−data Expr lit
48+data R = R R
27− = Lit lit
28− | Add Expr Expr
29− | Mul Expr Expr
30− deriving (Eq, Show, Generic)
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
31
63
32−instance
64+Which means that there is no finite term for `R` because it is recursive in all
33− ( Arbitrary lit
65+it's constructors. But, if you correct the definition of `R` like this.
34− , Arg (Expr lit) lit
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
35− ) => Arbitrary (Expr lit) where
132+instance (Arg (A a) a, Arbitrary a) => Arbitrary (A a) where
36 arbitrary = genericArbitrary
133 arbitrary = genericArbitrary
37 shrink = genericShrink
134 shrink = genericShrink
38```
135```
136+
137+Now everything compiles and works as expected.
src/Test/QuickCheck/Arbitrary/Generic.hs+4
@@ -168,6 +168,8 @@ import Test.QuickCheck.Arbitrary (GSubterms, RecursivelyShrink)
168-- } deriving (Generic)
168-- } deriving (Generic)
169-- deriving (Arbitrary) via GenericArbitrary Foo
169-- deriving (Arbitrary) via GenericArbitrary Foo
170-- @
170-- @
171+--
172+-- @since 1.0.0
171newtype GenericArbitrary a = GenericArbitrary { unGenericArbitrary :: a }
173newtype GenericArbitrary a = GenericArbitrary { unGenericArbitrary :: a }
172 deriving (Show, Eq)
174 deriving (Show, Eq)
173
175
@@ -192,6 +194,8 @@ instance
192-- arbitrary = genericArbitrary
194-- arbitrary = genericArbitrary
193-- shrink = genericShrink
195-- shrink = genericShrink
194-- @
196-- @
197+--
198+-- @since 1.0.0
195
199
196type Arg self field = (TypesDiffer self field ~ 'True)
200type Arg self field = (TypesDiffer self field ~ 'True)
197
201