MyNixOS website logo
Description

Generic implementation for QuickCheck's Arbitrary.

Generic implementations of methods of the Arbitrary class from the QuickCheck library. The approach taken here can lead to diverging instances for mutually recursive types but is safe for simply recursive ones and guarantees flat distribution for constructors of sum-types.

generic-arbitrary

Haskell-CI

What?

Package for deriving Arbitrary via Generic.

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

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

data R = R R
  deriving Generic

there is no instance

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.

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:

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

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

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

Now everything compiles and works as expected.

Metadata

Version

1.0.1

License

Platforms (75)

    Darwin
    FreeBSD
    Genode
    GHCJS
    Linux
    MMIXware
    NetBSD
    none
    OpenBSD
    Redox
    Solaris
    WASI
    Windows
Show all
  • aarch64-darwin
  • aarch64-genode
  • aarch64-linux
  • aarch64-netbsd
  • aarch64-none
  • aarch64_be-none
  • arm-none
  • armv5tel-linux
  • armv6l-linux
  • armv6l-netbsd
  • armv6l-none
  • armv7a-darwin
  • armv7a-linux
  • armv7a-netbsd
  • armv7l-linux
  • armv7l-netbsd
  • avr-none
  • i686-cygwin
  • i686-darwin
  • i686-freebsd
  • i686-genode
  • i686-linux
  • i686-netbsd
  • i686-none
  • i686-openbsd
  • i686-windows
  • javascript-ghcjs
  • loongarch64-linux
  • m68k-linux
  • m68k-netbsd
  • m68k-none
  • microblaze-linux
  • microblaze-none
  • microblazeel-linux
  • microblazeel-none
  • mips-linux
  • mips-none
  • mips64-linux
  • mips64-none
  • mips64el-linux
  • mipsel-linux
  • mipsel-netbsd
  • mmix-mmixware
  • msp430-none
  • or1k-none
  • powerpc-netbsd
  • powerpc-none
  • powerpc64-linux
  • powerpc64le-linux
  • powerpcle-none
  • riscv32-linux
  • riscv32-netbsd
  • riscv32-none
  • riscv64-linux
  • riscv64-netbsd
  • riscv64-none
  • rx-none
  • s390-linux
  • s390-none
  • s390x-linux
  • s390x-none
  • vc4-none
  • wasm32-wasi
  • wasm64-wasi
  • x86_64-cygwin
  • x86_64-darwin
  • x86_64-freebsd
  • x86_64-genode
  • x86_64-linux
  • x86_64-netbsd
  • x86_64-none
  • x86_64-openbsd
  • x86_64-redox
  • x86_64-solaris
  • x86_64-windows