Skip to content

Commit fff49dc

Browse files
committed
more docs
1 parent f277ba7 commit fff49dc

File tree

2 files changed

+115
-12
lines changed

2 files changed

+115
-12
lines changed

README.md

Lines changed: 111 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,31 @@
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
810
import GHC.Generics (Generic)
911
import Test.QuickCheck
1012
import 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
1229
data 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.

src/Test/QuickCheck/Arbitrary/Generic.hs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,8 @@ import Test.QuickCheck.Arbitrary (GSubterms, RecursivelyShrink)
168168
-- } deriving (Generic)
169169
-- deriving (Arbitrary) via GenericArbitrary Foo
170170
-- @
171+
--
172+
-- @since 1.0.0
171173
newtype GenericArbitrary a = GenericArbitrary { unGenericArbitrary :: a }
172174
deriving (Show, Eq)
173175

@@ -192,6 +194,8 @@ instance
192194
-- arbitrary = genericArbitrary
193195
-- shrink = genericShrink
194196
-- @
197+
--
198+
-- @since 1.0.0
195199

196200
type Arg self field = (TypesDiffer self field ~ 'True)
197201

0 commit comments

Comments
 (0)