0 | module Hedgehog.Internal.Function
  1 |
  2 | import Data.Colist
  3 | import Data.Cotree
  4 | import Data.Either
  5 | import Data.Nat
  6 | import Data.String
  7 |
  8 | import Hedgehog.Internal.Gen
  9 | import Hedgehog.Internal.Range
 10 | import Hedgehog.Internal.Util
 11 |
 12 | import System.Random.Pure.StdGen
 13 |
 14 | %default total
 15 |
 16 | ||| An interface for co-generators, somewhat an inverse of a generator
 17 | |||
 18 | ||| Generators, roughly, using a random seed produce a value of a certain type.
 19 | ||| Co-generators conversly, roughtly speaking, produce a random seed using
 20 | ||| a value of a certain type.
 21 | |||
 22 | ||| Due to technical properties of a seed type, instead of generating a seed
 23 | ||| value from stratch we perturb some existing value.
 24 | ||| Thus, a function of this interface produces a `StdGen -> StdGen` function
 25 | ||| being given a value of an appropriate type.
 26 | |||
 27 | ||| In some understanding, co-generators classify values of a given type, which
 28 | ||| allow to tune generators of other types.
 29 | ||| This gives an ability to generate functions of type `a -> b` being given
 30 | ||| a generator of type `b` and a co-generator of type `a`.
 31 | ||| Having a value of type `a`, co-generator can deterministically tune
 32 | ||| the generator of type `b` by perturbing a random seed that is used by the
 33 | ||| generator and use its output as an output for a function.
 34 | public export
 35 | interface Cogen a where
 36 |   constructor MkCogen
 37 |   perturb : a -> StdGen -> StdGen
 38 |
 39 | ||| This function perturbs the given seed both with `variant` and `split`.
 40 | |||
 41 | ||| This function is meant to be used between successive perturbations
 42 | ||| of different arguments of the same constructor.
 43 | |||
 44 | ||| It is designed to not commute when perturbation actions of a constructor's
 45 | ||| arguments do the same.
 46 | ||| Consider if `Cogen` interface is implemented for `Maybe a` and `Bool`
 47 | ||| in the following way:
 48 | |||
 49 | ||| ```
 50 | ||| Cogen a => Cogen (Maybe a) where
 51 | |||   perturb (Just x) = perturb x . variant 0
 52 | |||   perturb Nothing  = variant 1
 53 | |||
 54 | ||| Cogen Bool where
 55 | |||   perturb False = variant 0
 56 | |||   perturb True  = variant 1
 57 | ||| ```
 58 | |||
 59 | ||| In this case values `Nothing` and `Just True` would give the same
 60 | ||| perturbation to a seed, which is not optimal. Insertion of `shiftArg`
 61 | ||| before each call for `perturb` of a constructor argument would give
 62 | ||| different perturbations for different combinations of constructors and
 63 | ||| their arguments (unless you are very unlucky).
 64 | ||| Combination of both `variant` and `split` in the `shiftArg` function
 65 | ||| gives relative independence on how `perturb` of a constructor argument
 66 | ||| type is implemented.
 67 | export
 68 | shiftArg : StdGen -> StdGen
 69 | shiftArg = variant 33 . snd . split . variant 31
 70 |
 71 | ||| Changes random distribution of a generator of type `b`
 72 | ||| based on a value of type `a`
 73 | |||
 74 | ||| Change of distribution is done by a perturbation of a random seed,
 75 | ||| which is based on a `Cogen` implementation for the type `a`.
 76 | export
 77 | cogen : Cogen a => a -> Gen b -> Gen b
 78 | cogen x g = MkGen $ \sz, sd => unGen g sz $ perturb x sd
 79 |
 80 | export
 81 | Cogen Unit where
 82 |   perturb _ = id
 83 |
 84 | export
 85 | Cogen (Equal x y) where
 86 |   perturb Refl = id
 87 |
 88 | export
 89 | Cogen Bool where
 90 |   perturb True  = variant 0
 91 |   perturb False = variant 1
 92 |
 93 | export
 94 | Cogen Nat where
 95 |   perturb = variant
 96 |
 97 | export
 98 | Cogen Integer where
 99 |   perturb = variant . cast
100 |
101 | export
102 | Cogen Bits64 where
103 |   perturb = variant . cast
104 |
105 | export
106 | Cogen Bits16 where perturb = variant . cast
107 |
108 | export
109 | Cogen Bits8 where perturb = variant . cast
110 |
111 | export
112 | Cogen Int64 where perturb = variant . cast
113 |
114 | export
115 | Cogen Int16 where perturb = variant . cast
116 |
117 | export
118 | Cogen Int8 where perturb = variant . cast
119 |
120 | export
121 | Cogen Int where perturb = variant . cast
122 |
123 | export
124 | Cogen Char where perturb = variant . cast
125 |
126 | export
127 | Cogen Void where
128 |   perturb _ _ impossible
129 |
130 | export
131 | Cogen a => Cogen b => Cogen (a, b) where
132 |   perturb (x, y) = perturb x . shiftArg . perturb y . shiftArg
133 |
134 | export
135 | Cogen a => Cogen b => Cogen (Either a b) where
136 |   perturb $ Left  x = perturb x . shiftArg . variant 0
137 |   perturb $ Right y = perturb y . shiftArg . variant 1
138 |
139 | export
140 | Cogen a => Cogen (Maybe a) where
141 |   perturb Nothing  = variant 0
142 |   perturb (Just x) = perturb x . shiftArg . variant 1
143 |
144 | export
145 | Cogen a => Cogen (List a) where
146 |   perturb []      = variant 0
147 |   perturb (x::xs) = perturb xs . shiftArg . perturb x . shiftArg . variant 1
148 |
149 | export
150 | Cogen String where
151 |   perturb = perturb . fastUnpack
152 |
153 | ||| Generates a random function being given a generator of codomain type
154 | |||
155 | ||| This function takes a co-generator of domain type using `auto`-argument
156 | ||| based on the type.
157 | ||| This generator does not shrink.
158 | |||
159 | ||| Notice that this generator returns a non-showable value (unless you invent
160 | ||| your own implementation).
161 | ||| If you need a showable function, you have to use a shrinkable version,
162 | ||| which requires more strict implementation on the domain type.
163 | export
164 | function_ : Cogen a => Gen b -> Gen (a -> b)
165 | function_ bg =
166 |   MkGen $ \sz, sd => singleton $ \x => value $ unGen bg sz $ perturb x sd
167 |
168 | ||| Generates a random dependently typed function being given a generator
169 | ||| of codomain type family
170 | |||
171 | ||| This function takes a co-generator of domain type using `auto`-argument
172 | ||| based on the type.
173 | ||| This generator does not shrink.
174 | |||
175 | ||| Notice that this generator returns a non-showable value (unless you invent
176 | ||| your own implementation).
177 | export
178 | depfun_ :
179 |      {auto _ : Cogen a}
180 |   -> {0 b : a -> Type}
181 |   -> ((x : a) -> Gen $ b x)
182 |   -> Gen ((x : a) -> b x)
183 | depfun_ bg =
184 |   MkGen $ \sz, sd => singleton $ \x => value $ unGen (bg x) sz $ perturb x sd
185 |
186 | ||| Generates a random function with dependently typed domain being given a
187 | ||| generator of codomain type
188 | |||
189 | ||| This function takes a co-generator of domain type family using
190 | ||| `auto`-argument based on the type.
191 | ||| This generator does not shrink.
192 | |||
193 | ||| Notice that this generator returns a non-showable value (unless you invent
194 | ||| your own implementation).
195 | export
196 | dargfun_ :
197 |      {0 b    : a -> Type}
198 |   -> {auto _ : {0 x : a} -> Cogen (b x)}
199 |   -> Gen c
200 |   -> Gen ({0 x : a} -> b x -> c)
201 | dargfun_ bg =
202 |   MkGen $ \sz, sd => singleton $ \x => value $ unGen bg sz $ perturb x sd
203 |
204 | ||| Generates a random dependently typed function with dependently typed domain
205 | ||| being given a generator of codomain type family
206 | |||
207 | ||| This function takes a co-generator of domain type family using
208 | ||| `auto`-argument based on the type.
209 | ||| This generator does not shrink.
210 | |||
211 | ||| Notice that this generator returns a non-showable value (unless you invent
212 | ||| your own implementation).
213 | export
214 | dargdepfun_ :
215 |      {0 b : a -> Type}
216 |   -> {0 c : {0 x : a} -> b x -> Type}
217 |   -> {auto _ : {0 x : a} -> Cogen (b x)}
218 |   -> ({0 x : a} -> (y : b x) -> Gen (c y))
219 |   -> Gen ({0 x : a} -> (y : b x) -> c y)
220 | dargdepfun_ bg =
221 |   MkGen $ \sz, sd => singleton $ \x => value $ unGen (bg x) sz $ perturb x sd
222 |
223 | ----------------------------
224 | --- Shrinkable functions ---
225 | ----------------------------
226 |
227 | -- Claessen, K. Shrinking and showing functions:(functional pearl).
228 | -- In ACM SIGPLAN Notices (Vol. 47, No. 12, pp. 73-80). ACM. 2012, September
229 |
230 | export infixr 5 :->
231 |
232 | ||| A type of reified partial functions that can be represented isomorphic
233 | ||| to a function defined by pattern matching of an ADT
234 | ||| or any type that *can* implement `Generic` (but does not have to).
235 | |||
236 | ||| This type describes internal structure of such functions,
237 | ||| e.g. storing separately "vertical" and "horizontal" matching,
238 | ||| thus allowing to inspect, modify and simplify them,
239 | ||| for example, for showing and shrinking.
240 | public export
241 | data (:->) : Type -> Type -> Type where
242 |   FUnit : c -> () :-> c
243 |   FNil  : a :-> c
244 |   FPair : Lazy (a :-> b :-> c) -> (a, b) :-> c
245 |   FSum  : Lazy (a :-> c) -> Lazy (b :-> c) -> Either a b :-> c
246 |   FMap  : (a -> b) -> (b -> a) -> Lazy (b :-> c) -> a :-> c
247 |
248 | export
249 | Functor ((:->) a) where
250 |   map f $ FUnit c    = FUnit $ f c
251 |   map _ $ FNil       = FNil
252 |   map f $ FPair a    = FPair $ map (assert_total $ map f) a
253 |   map f $ FSum a b   = FSum (map f a) (map f b)
254 |   map f $ FMap a b c = FMap a b $ map f c
255 |
256 | table : a :-> c -> List (a, c)
257 | table (FUnit c) = [((), c)]
258 | table FNil      = []
259 | table (FPair f) = do
260 |   (a, bc) <- table f
261 |   (b, c) <- assert_total table bc
262 |   pure ((a, b), c)
263 | table (FSum a b) =
264 |      [(Left x, c)  | (x, c) <- table a]
265 |   ++ [(Right x, c) | (x, c) <- table b]
266 | table (FMap _ g a) = mapFst g <$> table a
267 |
268 | public export
269 | interface Cogen a => ShrCogen a where
270 |   constructor MkShrCogen
271 |   build : (a -> c) -> a :-> c
272 |
273 | export
274 | ShrCogen Void where
275 |   build _ = FNil
276 |
277 | export
278 | ShrCogen Unit where
279 |   build f = FUnit $ f ()
280 |
281 | export
282 | ShrCogen a => ShrCogen b => ShrCogen (a, b) where
283 |   build f = FPair $ build $ \a => build $ \b => f (a, b)
284 |
285 | export
286 | ShrCogen a => ShrCogen b => ShrCogen (Either a b) where
287 |   build f = FSum (build $ f . Left) (build $ f . Right)
288 |
289 | ||| Implements `build` function for a type through isomorphism
290 | ||| to a type that implements `ShrCogen`
291 | |||
292 | ||| Notice that `via f g` will only be well-behaved if
293 | ||| `g . f` and `f . g` are both identity functions.
294 | export
295 | via : ShrCogen b => (a -> b) -> (b -> a) -> (a -> c) -> a :-> c
296 | via a b f = FMap a b $ build $ f . b
297 |
298 | export
299 | ShrCogen a => ShrCogen (Maybe a) where
300 |   build = via (maybeToEither ()) eitherToMaybe
301 |
302 | export
303 | ShrCogen Bool where
304 |   build = via toEither fromEither
305 |
306 |     where
307 |       toEither : Bool -> Either Unit Unit
308 |       toEither True  = Left ()
309 |       toEither False = Right ()
310 |       fromEither : Either Unit Unit -> Bool
311 |       fromEither $ Left ()  = True
312 |       fromEither $ Right () = False
313 |
314 | export
315 | ShrCogen a => ShrCogen (List a) where
316 |   build = assert_total via toEither fromEither
317 |
318 |     where
319 |       toEither : List a -> Either Unit (a, List a)
320 |       toEither []      = Left ()
321 |       toEither (x::xs) = Right (x, xs)
322 |       fromEither : Either Unit (a, List a) -> List a
323 |       fromEither (Left ())       = []
324 |       fromEither (Right (x, xs)) = x::xs
325 |
326 | export
327 | ShrCogen (Equal x x) where
328 |   build = via (const ()) (const Refl)
329 |
330 | export
331 | ShrCogen Integer where
332 |   build = via toBits fromBits
333 |
334 |     where
335 |       toBits : Integer -> (Bool, List Bool)
336 |       toBits n = if n >= 0 then (True, go [] n) else (False, go [] $ -n - 1)
337 |
338 |         where
339 |           go : List Bool -> Integer -> List Bool
340 |           go bits x =
341 |             if x == 0
342 |               then bits
343 |               else go ((mod x 2 == 1) :: bits) (assert_smaller x $ div x 2)
344 |
345 |       fromBits : (Bool, List Bool) -> Integer
346 |       fromBits (sign, bits) = do
347 |         let body = foldl (\acc, b => acc * the Integer 2 + if b then 1 else 0) 0 bits
348 |         if sign then body else negate $ body + 1
349 |
350 | export
351 | ShrCogen Nat where build = via {b=Integer} cast cast
352 |
353 | export
354 | ShrCogen Int where build = via {b=Integer} cast cast
355 |
356 | export
357 | ShrCogen Int8 where build = via {b=Integer} cast cast
358 |
359 | export
360 | ShrCogen Int16 where build = via {b=Integer} cast cast
361 |
362 | export
363 | ShrCogen Int64 where build = via {b=Integer} cast cast
364 |
365 | export
366 | ShrCogen Bits8 where build = via {b=Nat} cast cast
367 |
368 | export
369 | ShrCogen Bits16 where build = via {b=Nat} cast cast
370 |
371 | export
372 | ShrCogen Bits64 where build = via {b=Nat} cast cast
373 |
374 | export
375 | ShrCogen Char where build = via {b=Nat} cast cast
376 |
377 | export
378 | ShrCogen String where build = via fastUnpack fastPack
379 |
380 | apply' : a :-> b -> a -> Maybe b
381 | apply' (FUnit c)    ()        = Just c
382 | apply' FNil         _         = Nothing
383 | apply' (FPair f)    (a, b)    = assert_total $ apply' !(apply' f a) b
384 | apply' (FSum f _)   (Left a)  = apply' f a
385 | apply' (FSum _ g)   (Right a) = apply' g a
386 | apply' (FMap f _ g) a         = apply' g (f a)
387 |
388 | shrinkFn : (b -> Inf (Colist b)) -> a :-> b -> Colist $ a :-> b
389 | shrinkFn shr (FUnit a)  = FUnit <$> shr a
390 | shrinkFn _   FNil       = []
391 | shrinkFn shr (FPair f)  =
392 |   shrinkFn (delay . assert_total (shrinkFn shr)) f <&>
393 |     \case FNil => FNila => FPair a
394 | shrinkFn shr (FSum a b) =
395 |   map (\case FSum FNil FNil => FNilx => x) $
396 |     (if notFNil b then [ FSum a FNil ] else []) ++
397 |     (if notFNil a then [ FSum FNil b ] else []) ++
398 |     map (`FSum` b) (shrinkFn shr a) ++
399 |     map (a `FSum`) (shrinkFn shr b)
400 |
401 |   where
402 |     (++) : forall a. Colist a -> Inf (Colist a) -> Colist a
403 |     []      ++ ys = ys
404 |     (x::xs) ++ ys = x :: (xs ++ ys)
405 |
406 |     notFNil : forall a, b. a :-> b -> Bool
407 |     notFNil FNil = False
408 |     notFNil _   = True
409 | shrinkFn shr (FMap f g a) =
410 |   shrinkFn shr a <&> \case FNil => FNilx => FMap f g x
411 |
412 | ||| The type for a total randomly-generated function
413 | export
414 | data Fn a b = MkFn b (a :-> Cotree b)
415 |
416 | export
417 | Show a => Show b => Show (Fn a b) where
418 |   show (MkFn xb xa) = unlines $ (table xa <&> showCase) ++ ["_ -> " ++ show xb]
419 |
420 |     where
421 |       showCase : (a, Cotree b) -> String
422 |       showCase (lhs, rhs) = show lhs ++ " -> " ++ show rhs.value
423 |
424 | ||| Generates a random function being given a generator of codomain type
425 | |||
426 | ||| The generated function is returned in a showable type `Fn a b`.
427 | |||
428 | ||| This function takes a co-generator of domain type using `auto`-argument
429 | ||| based on the type.
430 | ||| This generator is shrinkable. For this, it requires additional `Arg`
431 | ||| argument.
432 | export
433 | function : ShrCogen a => Gen b -> Gen (Fn a b)
434 | function gb = [| MkFn gb (genFn $ \a => cogen a gb) |]
435 |
436 |   where
437 |     genFn : (a -> Gen b) -> Gen (a :-> Cotree b)
438 |     genFn g = MkGen $ \sz, sd =>
439 |       iterate (shrinkFn forest) . map (runGen sz sd) $ build g
440 |
441 | ||| Coverts a showable randomly generated function to an actual function
442 | export
443 | apply : Fn a b -> a -> b
444 | apply (MkFn b f) = maybe b value . apply' f
445 |
446 | ||| Generates a random function being given a generator of codomain type
447 | |||
448 | ||| This generator is shrinkable
449 | |||
450 | ||| This is a wrapper of a `function` generator.
451 | ||| It may be useful sometimes, however, it returnes a non-showable type.
452 | ||| To use functions generator in `forAll` in a property, use `function`
453 | ||| generator.
454 | public export
455 | function' : ShrCogen a => Gen b -> Gen (a -> b)
456 | function' = map apply . function
457 |