liveBook · Manning

Arbitrary instance

As explained in Learn Haskell by Example

Arbitrary is the QuickCheck type class whose instances specify how to generate and shrink values of a type for property testing.

An Arbitrary instance tells QuickCheck how to create test inputs for a type and how to reduce failing inputs to simpler candidates. Its required method is arbitrary :: Gen a; shrink :: a -> [a] is optional at first, but makes failing property tests easier to inspect. Generator selection is driven by types, so a polymorphic generator can reuse existing instances for its component types. section 9.4 section 9.6

ghci> :i Arbitrary
type Arbitrary :: * -> Constraint
class Arbitrary a where
  arbitrary :: Gen a
  shrink :: a -> [a]
  {-# MINIMAL arbitrary #-}

The class interface separates random generation from deterministic shrinking. Implement arbitrary to make a type generatable; add shrink to improve counterexamples. section 9.4

Build generators with Gen combinators and do notation. arbitrary supplies values from an available instance, while choose, elements, vectorOf, and suchThat shape them into valid test data. Size can bound complexity: an alphabet-rotation generator chooses a nonempty alphabet, a size-related rotation amount, and a character from that alphabet. section 9.4.1

Preserving an AssocMap invariant

For an AssocMap backed by [(k, v)], generate arbitrary keys, remove duplicates, generate an equal number of arbitrary values, and zip them together. The instance needs Eq k to enforce unique keys plus Arbitrary k and Arbitrary v to obtain component values. Its shrinker can shrink the backing list, then remove duplicate keys again so every candidate remains a valid map. section 9.4.2 section 9.4.3

A useful Arbitrary instance follows two rules:

  • Generate values that satisfy the type’s invariants rather than merely matching its representation. section 9.4.2
  • Return simpler deterministic shrink candidates without returning the original value, or shrinking can loop. section 9.4.3
  • Recheck each shrink candidate against the failing property until no smaller falsifying input remains. section 9.4.3

Shrinking is not another random generation pass. Given a failing value, shrink produces a predetermined list of alternatives, and QuickCheck tests those alternatives to locate a minimal counterexample. A custom shrinker must preserve the same invariants as the generator, since a malformed candidate can obscure the failure being diagnosed. section 9.4.3

How Learn Haskell by Example covers it

How deep it goes
Defines both class methods, builds custom Gen values, and implements invariant-preserving generation and shrinking for AssocMap.
Examples
Demonstrates generated Int, lists, Maybe, Either, alphabet rotations, and AssocMap values with unique keys.
What it assumes
Uses existing Arbitrary instances, Eq constraints, Gen combinators, and do notation to define polymorphic instances.
What you can do after
Apply it via QuickCheck by defining generators and shrinkers that preserve a custom type’s invariants.
How it connects
Connects property testing with Gen, shrink, type classes, Functor mapping, and size-controlled generators.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Learn Haskell by Example. The section links point to the parts of the book it draws on.