File: GenericArbitrary.hs

package info (click to toggle)
haskell-text-show 3.10.5-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,148 kB
  • sloc: haskell: 8,817; ansic: 23; makefile: 6
file content (147 lines) | stat: -rw-r--r-- 4,828 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
{-# LANGUAGE FlexibleContexts     #-}
{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE KindSignatures       #-}
{-# LANGUAGE MagicHash            #-}
{-# LANGUAGE ScopedTypeVariables  #-}
{-# LANGUAGE TypeOperators        #-}
{-# LANGUAGE TypeSynonymInstances #-}

{-|
Module:      Instances.Utils.GenericArbitrary
Copyright:   (C) 2014-2017 Ryan Scott
License:     BSD-style (see the file LICENSE)
Maintainer:  Ryan Scott
Stability:   Provisional
Portability: GHC

A generic default implemention of 'arbitrary'.

Ideally, this should be a part of @QuickCheck@ itself
(see https://github.com/nick8325/quickcheck/pull/40), but alas, it hasn't been
merged yet. Until then, we'll have to define it ourselves.
-}
module Instances.Utils.GenericArbitrary (genericArbitrary) where

import Generics.Deriving.Base

import GHC.Exts (Char(..), Double(..), Float(..), Int(..), Word(..))

import Prelude ()
import Prelude.Compat

import Test.QuickCheck (Arbitrary(..), Gen, choose)

-- | `Gen` for generic instances in which each constructor has equal probability
-- of being chosen.
genericArbitrary :: (Generic a, GArbitrary (Rep a)) => Gen a
genericArbitrary = to <$> gArbitrary

class GArbitrary f where
  gArbitrary :: Gen (f a)

instance GArbitrary V1 where
  -- Following the `Encode' V1` example in GHC.Generics.
  gArbitrary = undefined

instance GArbitrary U1 where
  gArbitrary = return U1

instance (GArbitrary a, GArbitrary b) => GArbitrary (a :*: b) where
  gArbitrary = (:*:) <$> gArbitrary <*> gArbitrary

instance ( SumSize    a, SumSize    b
         , ChooseSum  a, ChooseSum  b ) => GArbitrary (a :+: b) where
  gArbitrary = do
    -- We cannot simply choose with equal probability between the left and
    -- right part of the `a :+: b` (e.g. with `choose (False, True)`),
    -- because GHC.Generics does not guarantee :+: to be balanced; even if it
    -- did, it could only do so for sum types with 2^n alternatives.
    -- If we did that and got a data structure of form `(a :+: (b :+: c))`,
    -- then a would be chosen just as often as b and c together.
    -- So we first have to compute the number of alternatives using `sumSize`,
    -- and then uniformly sample a number in the corresponding range.
    let size = unTagged2 (sumSize :: Tagged2 (a :+: b) Int)
    x <- choose (1, size)
    -- Optimisation:
    -- We could just recursively call `gArbitrary` on the left orright branch
    -- here, as in
    --   if x <= sizeL
    --     then L1 <$> gArbitrary
    --     else R1 <$> gArbitrary
    -- but this would unnecessarily sample again in the same sum type, and that
    -- even though `x` completely determines which alternative to choose,
    -- and sampling is slow because it needs IO and random numbers.
    -- So instead we use `chooseSum x` to pick the x'th alternative from the
    -- current sum type.
    -- This made it around 50% faster for a sum type with 26 alternatives
    -- on my computer.
    chooseSum x

instance GArbitrary a => GArbitrary (M1 i c a) where
  gArbitrary = M1 <$> gArbitrary

instance Arbitrary a => GArbitrary (K1 i a) where
  gArbitrary = K1 <$> arbitrary

instance GArbitrary UChar where
  gArbitrary = do
    C# c <- arbitrary
    return (UChar c)

instance GArbitrary UDouble where
  gArbitrary = do
    D# d <- arbitrary
    return (UDouble d)

instance GArbitrary UFloat where
  gArbitrary = do
    F# f <- arbitrary
    return (UFloat f)

instance GArbitrary UInt where
  gArbitrary = do
    I# i <- arbitrary
    return (UInt i)

instance GArbitrary UWord where
  gArbitrary = do
    W# w <- arbitrary
    return (UWord w)

newtype Tagged2 (s :: * -> *) b = Tagged2 {unTagged2 :: b}

-- | Calculates the size of a sum type (numbers of alternatives).
--
-- Example: `data X = A | B | C` has `sumSize` 3.
class SumSize f where
  sumSize :: Tagged2 f Int

-- Recursive case: Sum split `(:+:)`..
instance (SumSize a, SumSize b) => SumSize (a :+: b) where
  sumSize = Tagged2 $ unTagged2 (sumSize :: Tagged2 a Int) +
                          unTagged2 (sumSize :: Tagged2 b Int)
  {-# INLINE sumSize #-}

-- Constructor base case.
instance SumSize (C1 s a) where
  sumSize = Tagged2 1
  {-# INLINE sumSize #-}

-- | This class takes an integer `x` and returns a `gArbitrary` value
-- for the `x`'th alternative in a sum type.
class ChooseSum f where
  chooseSum :: Int -> Gen (f a)

-- Recursive case: Check whether `x` lies in the left or the right side
-- of the (:+:) split.
instance (SumSize a, ChooseSum a, ChooseSum b) => ChooseSum (a :+: b) where
  chooseSum x = do
    let sizeL = unTagged2 (sumSize :: Tagged2 a Int)
    if x <= sizeL
      then L1 <$> chooseSum x
      else R1 <$> chooseSum (x - sizeL)

-- Constructor base case.
instance (GArbitrary a) => ChooseSum (C1 s a) where
  chooseSum 1 = gArbitrary
  chooseSum _ = error "chooseSum: BUG"