An open API service indexing awesome lists of open source software.

https://github.com/typeable/generic-arbitrary

Generic method implementations for Arbitrary
https://github.com/typeable/generic-arbitrary

Last synced: 10 months ago
JSON representation

Generic method implementations for Arbitrary

Awesome Lists containing this project

README

          

# generic-arbitrary

[![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)

# What?

Package for deriving `Arbitrary` via `Generic`.

``` haskell
import GHC.Generics (Generic)
import Test.QuickCheck
import Test.QuickCheck.Arbitrary.Generic

data Expr
= Lit Int
| Add Expr Expr
| Mul Expr Expr
deriving (Eq, Show, Generic)
deriving Arbitrary via (GenericArbitrary Expr)
```

Older versions of this package had a problem with hanging `arbitrary`
method. Since `1.0.0` this problem almost solved.

For `QuickCheck` older than `2.14.0` the `GenericArbitrary` is not available, so
you will need to write instances more verbosely

``` haskell
data Expr
= Lit Int
| Add Expr Expr
| Mul Expr Expr
deriving (Eq, Show, Generic)

instance Arbitrary Expr where
arbitrary = genericArbitrary
shrink = genericShrink
```

Which is generally the same.

# Infinite terms problem

The `generic-arbitrary` can partially handle the problem with recursive
types. Assume the type `R`

``` haskell
data R = R R
deriving Generic
```

there is no instance

``` haskell
instance Arbitrary R where
arbitrary = genericArbitrary
shrink = genericShrink
```

If you try to compile this you will get a type level error

> • R refers to itself in all constructors

Which means that there is no finite term for `R` because it is recursive in all
it's constructors. But, if you correct the definition of `R` like this.

``` haskell
data R = R R | F
deriving Generic
```

Then it will compile. And the `arbitrary` generated will not hang forever,
because it respects the `size` parameter.

## Limitation

There is a limitation of recursion detection:

``` haskell
data R1 = R1 R2
deriving (Eq, Ord, Show, Generic)
deriving anyclass NFData
deriving Arbitrary via (GenericArbitrary R1)

data R2 = R2 R1
deriving (Eq, Ord, Show, Generic)
deriving anyclass NFData
deriving Arbitrary via (GenericArbitrary R2)
```

This code will compile and the `arbitrary` generated will always hang. Yes,
there is a problem with mutually recursive types.

# Type parameters

Now let's see an example of datatype with parameters

``` haskell
data A a = A a
deriving (Eq, Ord, Show)
deriving anyclass NFData
deriving (Generic)

instance (Arbitrary a) => Arbitrary (A a) where
arbitrary = genericArbitrary
shrink = genericShrink
```

It should work from first glance, but when compile it will throw an error:

```
• Could not deduce (Test.QuickCheck.Arbitrary.Generic.GArbitrary
(A a)
(GHC.Generics.D1
('GHC.Generics.MetaData "A" "ParametersTest" "main" 'False)
(GHC.Generics.C1
('GHC.Generics.MetaCons "A" 'GHC.Generics.PrefixI 'False)
(GHC.Generics.S1
('GHC.Generics.MetaSel
'Nothing
'GHC.Generics.NoSourceUnpackedness
'GHC.Generics.NoSourceStrictness
'GHC.Generics.DecidedLazy)
(GHC.Generics.Rec0 a))))
(TypesDiffer (A a) a))
arising from a use of ‘genericArbitrary’
```

Here the `TypesDiffer` is a type familty dealing with recursive types and
helping us to eliminate inproper instances. To convince the compiller, that the
`a` parameter is not an `A a` we must fix the instance with additional
constraint `Arg (A a) a`

``` haskell
instance (Arg (A a) a, Arbitrary a) => Arbitrary (A a) where
arbitrary = genericArbitrary
shrink = genericShrink
```

Now everything compiles and works as expected.