packages feed

generic-arbitrary-1.0.0: README.md

# 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.