├── .gitignore ├── elm-package.json ├── LICENSE ├── README.md └── src └── Shrink.elm /.gitignore: -------------------------------------------------------------------------------- 1 | elm-stuff 2 | -------------------------------------------------------------------------------- /elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "summary": "Library for authoring shrinking strategies", 4 | "repository": "https://github.com/elm-community/shrink.git", 5 | "license": "BSD-3-Clause", 6 | "source-directories": [ 7 | "src" 8 | ], 9 | "exposed-modules": [ 10 | "Shrink" 11 | ], 12 | "dependencies": { 13 | "elm-community/lazy-list": "1.0.0 <= v < 2.0.0", 14 | "elm-lang/core": "5.0.0 <= v < 6.0.0", 15 | "elm-lang/lazy": "2.0.0 <= v < 3.0.0" 16 | }, 17 | "elm-version": "0.18.0 <= v < 0.19.0" 18 | } 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Elm Community 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shrinking strategies with elm-community/shrink 2 | 3 | `shrink` is a library for using and creating shrinking strategies. A 4 | shrinking strategy, or shrinker, is a way of taking a value and producing a 5 | list of values which are, some sense, more minimal than the original value. 6 | 7 | Shrinking is heavily used in property-based testing as a way to shrink 8 | failing test cases into a more minimal test case. This is key for being 9 | able to debug code swiftly and easily. Therefore, the main intended use 10 | of this library is to support testing frameworks. You might also use it to 11 | define shrinkers for types you define, in order to test them better. 12 | 13 | Note that `shrink` uses lazy lists instead of lists. This means that `shrink` has a direct dependency on 14 | [elm-community/lazy-list](https://github.com/elm-community/lazy-list). 15 | 16 | ```elm 17 | type alias Shrinker a = a -> LazyList a 18 | ``` 19 | That is, a shrinker takes a value to shrink, and produces, *lazily*, a list 20 | of shrunken values. 21 | 22 | ### Basic Examples 23 | 24 | The following examples show how to use the basic shrinkers and the kinds of 25 | results they produce. Note that we're glossing over the lazy part of this; 26 | pretend there's a `Lazy.List.toList` on the left of each example. 27 | 28 | **Shrink an Int** 29 | 30 | ```elm 31 | int 10 == [0,5,7,8,9] 32 | ``` 33 | 34 | 35 | **Shrink a String** 36 | 37 | ```elm 38 | string "Hello World" == 39 | [""," World","Hellod","llo World","Heo World","HellWorld","Hello rld","Hello Wod","ello World","Hllo World","Helo World","Helo World","Hell World","HelloWorld","Hello orld","Hello Wrld","Hello Wold","Hello Word","Hello Worl","\0ello World","$ello World","6ello World","?ello World","Cello World","Eello World","Fello World","Gello World","H\0llo World","H2llo World","HKllo World","HXllo World","H^llo World","Hallo World","Hcllo World","Hdllo World","He\0lo World","He6lo World","HeQlo World","He^lo World","Heelo World","Hehlo World","Hejlo World","Heklo World","Hel\0o World","Hel6o World","HelQo World","Hel^o World","Heleo World","Helho World","Heljo World","Helko World","Hell\0 World","Hell7 World","HellS World","Hella World","Hellh World","Hellk World","Hellm World","Helln World","Hello\0World","HelloWorld","HelloWorld","HelloWorld","HelloWorld","HelloWorld","Hello \0orld","Hello +orld","Hello Aorld","Hello Lorld","Hello Qorld","Hello Torld","Hello Uorld","Hello Vorld","Hello W\0rld","Hello W7rld","Hello WSrld","Hello Warld","Hello Whrld","Hello Wkrld","Hello Wmrld","Hello Wnrld","Hello Wo\0ld","Hello Wo9ld","Hello WoUld","Hello Wocld","Hello Wojld","Hello Wonld","Hello Wopld","Hello Woqld","Hello Wor\0d","Hello Wor6d","Hello WorQd","Hello Wor^d","Hello Wored","Hello Worhd","Hello Worjd","Hello Workd","Hello Worl\0","Hello Worl2","Hello WorlK","Hello WorlW","Hello Worl]","Hello Worl`","Hello Worlb","Hello Worlc"] 40 | ``` 41 | 42 | **Shrink a Maybe Float** 43 | 44 | ```elm 45 | maybe float (Just 3.14) == 46 | [Nothing,Just 0,Just 1.57,Just 2.355,Just 2.7475,Just 2.94375,Just 3.041875,Just 3.0909375,Just 3.11546875,Just 3.127734375,Just 3.1338671875,Just 3.1369335937500002,Just 3.138466796875,Just 3.1392333984375,Just 3.1396166992187498,Just 3.1398083496093747] 47 | ``` 48 | 49 | **Shrink a List of Bools** 50 | 51 | ```elm 52 | list bool [True, False, False, True, False] == 53 | [[],[False,True,False],[True,False,False],[False,False,True,False],[True,False,True,False],[True,False,True,False],[True,False,False,False],[True,False,False,True],[False,False,False,True,False],[True,False,False,False,False]] 54 | ``` 55 | 56 | 57 | ## Make your own shrinkers 58 | 59 | With `shrink`, it is very easy to make your own shrinkers for your own data 60 | types. 61 | 62 | First of all, let's look at one of the basic shrinkers available in `shrink` 63 | and how it is implemented. 64 | 65 | **Shrinker Bool** 66 | 67 | To shrink a `Bool`, you have to consider the possible values of `Bool`: `True` 68 | and `False`. Intuitively, we understand that `False` is more "minimal" 69 | than `True`. As, such, we would shrink `True` to `False`. As for `False`, 70 | there is no value that is more "minimal" than `False`. As such, we simply 71 | shrink it to the empty list. 72 | 73 | ```elm 74 | bool : Shrinker Bool 75 | bool b = case b of 76 | True -> False ::: empty 77 | False -> empty 78 | ``` 79 | 80 | *Note that there is no "exact" rule to deciding on whether something is more 81 | "minimal" than another.* The idea is that you want to have one case return 82 | the empty list if possible while other cases move towards the more "minimal" 83 | cases. In this example, we decided that `False` was the more "minimal" case and, 84 | in a sense, moved `True` towards `False` since `False` then returns the empty 85 | list. Obviously, this choice could have been reversed and you would be 86 | justified in doing so. Just remember that *a value should never shrink to itself, 87 | or shrink to something that (through any number of steps) shrinks back to itself.* 88 | This is a recipe for an infinite loop. 89 | 90 | Now that we understand how to make a simple shrinker, let's see how we can use 91 | these simple shrinkers together to make something that can shrink a more 92 | complex data structure. 93 | 94 | **Shrinker Vector** 95 | 96 | Consider the following `Vector` type: 97 | 98 | ```elm 99 | type alias Vector = 100 | { x : Float 101 | , y : Float 102 | , z : Float 103 | } 104 | ``` 105 | 106 | Our goal is to produce a vector shrinker: 107 | 108 | ```elm 109 | vector : Shrinker Vector 110 | ``` 111 | 112 | `shrink` provides a basic `Float` shrinker and we can use it in combination 113 | with `map` and `andMap` to make the `Vector` shrinker. 114 | 115 | ```elm 116 | vector : Shrinker Vector 117 | vector {x, y, z} = 118 | Vector 119 | `map` float x 120 | `andMap` float y 121 | `andMap` float z 122 | ``` 123 | 124 | And voila! Super simple. Let's try this on an even larger structure. 125 | 126 | 127 | **Shrinker Mario** 128 | 129 | Consider the following types: 130 | 131 | ```elm 132 | type alias Mario = 133 | { position : Vector 134 | , velocity : Vector 135 | , direction : Direction 136 | } 137 | 138 | type alias Vector = 139 | { x : Float 140 | , y : Float 141 | } 142 | 143 | type Direction 144 | = Left 145 | | Right 146 | ``` 147 | 148 | And our goal is to produce a shrinker of Marios. 149 | 150 | ```elm 151 | mario : Shrinker Mario 152 | ``` 153 | 154 | To do this, we will split the steps. We can notice that we have two distinct 155 | data types we need to shrink: `Vector` and `Direction`. 156 | 157 | For `Vector`, we can use the approach from the previous example: 158 | 159 | ```elm 160 | vector : Shrinker Vector 161 | vector {x, y} = 162 | Vector 163 | `map` float x 164 | `andMap` float y 165 | ``` 166 | 167 | And for `Direction`, we can apply a similar approach to our `Bool` example: 168 | 169 | ```elm 170 | direction : Shrinker Direction 171 | direction dir = case dir of 172 | Left -> empty 173 | Right -> Left ::: empty 174 | ``` 175 | 176 | Where `Left` here is considered the "minimal" case. 177 | 178 | 179 | Now, let's put these together: 180 | 181 | ```elm 182 | mario : Shrinker Mario 183 | mario m = 184 | Mario 185 | `map` vector m.position 186 | `andMap` vector m.velocity 187 | `andMap` direction m.direction 188 | ``` 189 | 190 | And, yay! We now can shrink `Mario`! No mushrooms needed! 191 | 192 | ### One more technique 193 | 194 | Sometimes, you want to shrink a data structure but you know intuitively that 195 | it should shrink in a similar fashion to some other data structure. It would 196 | be nice if you could just convert back and from that other data structure and 197 | use its already existing shrinker. 198 | 199 | For example `List` and `Array`. 200 | 201 | In `shrink`, there exists a `List` shrinker: 202 | 203 | ```elm 204 | list : Shrinker a -> Shrinker (List a) 205 | ``` 206 | 207 | This shrinker is quite involved and does a number of things to shuffle elements, 208 | shrink some elements, preserve others, etc... 209 | 210 | It would be nice if that can be re-used for arrays, because in a high-level 211 | sense, arrays and lists are equivalent. 212 | 213 | This is exactly what `shrink` does and it uses a function called `convert`. 214 | 215 | ```elm 216 | convert : (a -> b) -> (b -> a) -> Shrinker a -> Shrinker b 217 | ``` 218 | 219 | `convert` converts a shrinker of a's into a shrinker of b's by taking a 220 | two functions to convert to and from b's. 221 | 222 | **IMPORTANT NOTE: Both functions must be perfectly invertible or else this 223 | process may create garbage!** 224 | 225 | By invertible, I mean that `f` and `g` are invertible **if and only if** 226 | 227 | ```elm 228 | f (g x) == g (f x) == x 229 | ``` 230 | 231 | **for all `x`.** 232 | 233 | Now we can very simply implement a shrinker of arrays as follows: 234 | 235 | ```elm 236 | array : Shrinker a -> Shrinker (Array a) 237 | array shrinker = 238 | convert (Array.fromList) (Array.toList) (list shrinker) 239 | ``` 240 | 241 | And, ta-da... 0 brain cells were used to get a shrinker on arrays. 242 | -------------------------------------------------------------------------------- /src/Shrink.elm: -------------------------------------------------------------------------------- 1 | module Shrink exposing (Shrinker, shrink, noShrink, unit, bool, order, int, atLeastInt, float, atLeastFloat, char, atLeastChar, character, string, maybe, result, list, lazylist, array, tuple, tuple3, tuple4, tuple5, convert, keepIf, dropIf, merge, map, andMap) 2 | 3 | {-| Library containing a collection of basic shrinking strategies and 4 | helper functions to help you construct shrinking strategies. 5 | 6 | # Shrinking Basics 7 | @docs Shrinker, shrink 8 | 9 | # Shrinkers 10 | @docs noShrink, unit, bool, order, int, atLeastInt, float, atLeastFloat, char, atLeastChar, character, string, maybe, result, lazylist, list, array, tuple, tuple3, tuple4, tuple5 11 | 12 | # Functions on Shrinkers 13 | @docs convert, keepIf, dropIf, merge, map, andMap 14 | 15 | -} 16 | 17 | import Lazy.List exposing (LazyList, (:::), (+++), empty) 18 | import Lazy exposing (Lazy, force, lazy) 19 | import List 20 | import Array exposing (Array) 21 | import Char 22 | import String 23 | 24 | 25 | {-| The shrinker type. 26 | A shrinker is a function that takes a value and returns a list of values that 27 | are in some sense "smaller" than the given value. If there are no such values 28 | conceptually, then the shrinker should just return the empty list. 29 | -} 30 | type alias Shrinker a = 31 | a -> LazyList a 32 | 33 | 34 | {-| Perform shrinking. Takes a predicate that returns `True` if you want 35 | shrinking to continue (e.g. the test failed). Also takes a shrinker and a value 36 | to shrink. It returns the shrunken value, or the input value if no shrunken 37 | values that satisfy the predicate are found. 38 | -} 39 | shrink : (a -> Bool) -> Shrinker a -> a -> a 40 | shrink keepShrinking shrinker originalVal = 41 | let 42 | helper lazyList val = 43 | case force lazyList of 44 | Lazy.List.Nil -> 45 | val 46 | 47 | Lazy.List.Cons head tail -> 48 | if keepShrinking head then 49 | helper (shrinker head) head 50 | else 51 | helper tail val 52 | in 53 | helper (shrinker originalVal) originalVal 54 | 55 | 56 | {-| Perform no shrinking. Equivalent to the empty lazy list. 57 | -} 58 | noShrink : Shrinker a 59 | noShrink _ = 60 | empty 61 | 62 | 63 | {-| Shrink the empty tuple. Equivalent to `noShrink`. 64 | -} 65 | unit : Shrinker () 66 | unit = 67 | noShrink 68 | 69 | 70 | {-| Shrinker of bools. 71 | -} 72 | bool : Shrinker Bool 73 | bool b = 74 | case b of 75 | True -> 76 | False ::: empty 77 | 78 | False -> 79 | empty 80 | 81 | 82 | {-| Shrinker of `Order` values. 83 | -} 84 | order : Shrinker Order 85 | order o = 86 | case o of 87 | GT -> 88 | EQ ::: LT ::: empty 89 | 90 | LT -> 91 | EQ ::: empty 92 | 93 | EQ -> 94 | empty 95 | 96 | 97 | {-| Shrinker of integers. 98 | -} 99 | int : Shrinker Int 100 | int n = 101 | if n < 0 then 102 | -n ::: Lazy.List.map ((*) -1) (seriesInt 0 -n) 103 | else 104 | seriesInt 0 n 105 | 106 | 107 | {-| Construct a shrinker of ints which considers the given int to 108 | be most minimal. 109 | -} 110 | atLeastInt : Int -> Shrinker Int 111 | atLeastInt min n = 112 | if n < 0 && n >= min then 113 | -n ::: Lazy.List.map ((*) -1) (seriesInt 0 -n) 114 | else 115 | seriesInt (max 0 min) n 116 | 117 | 118 | {-| Shrinker of floats. 119 | -} 120 | float : Shrinker Float 121 | float n = 122 | if n < 0 then 123 | -n ::: Lazy.List.map ((*) -1) (seriesFloat 0 -n) 124 | else 125 | seriesFloat 0 n 126 | 127 | 128 | {-| Construct a shrinker of floats which considers the given float to 129 | be most minimal. 130 | -} 131 | atLeastFloat : Float -> Shrinker Float 132 | atLeastFloat min n = 133 | if n < 0 && n >= min then 134 | -n ::: Lazy.List.map ((*) -1) (seriesFloat 0 -n) 135 | else 136 | seriesFloat (max 0 min) n 137 | 138 | 139 | {-| Shrinker of chars. 140 | -} 141 | char : Shrinker Char 142 | char = 143 | convert Char.fromCode Char.toCode int 144 | 145 | 146 | {-| Construct a shrinker of chars which considers the given char to 147 | be most minimal. 148 | -} 149 | atLeastChar : Char -> Shrinker Char 150 | atLeastChar char = 151 | convert Char.fromCode Char.toCode (atLeastInt (Char.toCode char)) 152 | 153 | 154 | {-| Shrinker of chars which considers the empty space as the most 155 | minimal char and omits the control key codes. 156 | 157 | Equivalent to: 158 | 159 | atLeastChar (Char.fromCode 32) 160 | -} 161 | character : Shrinker Char 162 | character = 163 | atLeastChar (Char.fromCode 32) 164 | 165 | 166 | {-| Shrinker of strings. Considers the empty string to be the most 167 | minimal string and the space to be the most minimal char. 168 | 169 | Equivalent to: 170 | 171 | convert String.fromList String.toList (list character) 172 | -} 173 | string : Shrinker String 174 | string = 175 | convert String.fromList String.toList (list character) 176 | 177 | 178 | {-| Maybe shrinker constructor. 179 | Takes a shrinker of values and returns a shrinker of Maybes. 180 | -} 181 | maybe : Shrinker a -> Shrinker (Maybe a) 182 | maybe shrink m = 183 | case m of 184 | Just a -> 185 | Nothing ::: Lazy.List.map Just (shrink a) 186 | 187 | Nothing -> 188 | empty 189 | 190 | 191 | {-| Result shrinker constructor. Takes a shrinker of errors and a shrinker of 192 | values and returns a shrinker of Results. 193 | -} 194 | result : Shrinker error -> Shrinker value -> Shrinker (Result error value) 195 | result shrinkError shrinkValue r = 196 | case r of 197 | Ok value -> 198 | Lazy.List.map Ok (shrinkValue value) 199 | 200 | Err error -> 201 | Lazy.List.map Err (shrinkError error) 202 | 203 | 204 | {-| Lazy List shrinker constructor. Takes a shrinker of values and returns a 205 | shrinker of Lazy Lists. The lazy list being shrunk must be finite. (I mean 206 | really, how do you shrink infinity?) 207 | -} 208 | lazylist : Shrinker a -> Shrinker (LazyList a) 209 | lazylist shrink l = 210 | lazy <| 211 | \() -> 212 | let 213 | n : Int 214 | n = 215 | Lazy.List.length l 216 | 217 | shrinkOne : LazyList a -> LazyList (LazyList a) 218 | shrinkOne l = 219 | lazy <| 220 | \() -> 221 | case force l of 222 | Lazy.List.Nil -> 223 | force empty 224 | 225 | Lazy.List.Cons x xs -> 226 | force 227 | (Lazy.List.map (flip (:::) xs) (shrink x) 228 | +++ Lazy.List.map ((:::) x) (shrinkOne xs) 229 | ) 230 | 231 | removes : Int -> Int -> Shrinker (LazyList a) 232 | removes k n l = 233 | lazy <| 234 | \() -> 235 | if k > n then 236 | force empty 237 | else if Lazy.List.isEmpty l then 238 | force (empty ::: empty) 239 | else 240 | let 241 | first = 242 | Lazy.List.take k l 243 | 244 | rest = 245 | Lazy.List.drop k l 246 | in 247 | force <| 248 | rest 249 | ::: Lazy.List.map ((+++) first) (removes k (n - k) rest) 250 | in 251 | force <| 252 | Lazy.List.andThen (\k -> removes k n l) 253 | (Lazy.List.takeWhile (\x -> x > 0) (Lazy.List.iterate (\n -> n // 2) n)) 254 | +++ shrinkOne l 255 | 256 | 257 | {-| List shrinker constructor. 258 | Takes a shrinker of values and returns a shrinker of Lists. 259 | -} 260 | list : Shrinker a -> Shrinker (List a) 261 | list shrink = 262 | convert Lazy.List.toList Lazy.List.fromList (lazylist shrink) 263 | 264 | 265 | {-| Array shrinker constructor. 266 | Takes a shrinker of values and returns a shrinker of Arrays. 267 | -} 268 | array : Shrinker a -> Shrinker (Array a) 269 | array shrink = 270 | convert Lazy.List.toArray Lazy.List.fromArray (lazylist shrink) 271 | 272 | 273 | {-| 2-Tuple shrinker constructor. 274 | Takes a tuple of shrinkers and returns a shrinker of tuples. 275 | -} 276 | tuple : ( Shrinker a, Shrinker b ) -> Shrinker ( a, b ) 277 | tuple ( shrinkA, shrinkB ) ( a, b ) = 278 | Lazy.List.map ((,) a) (shrinkB b) 279 | +++ Lazy.List.map (flip (,) b) (shrinkA a) 280 | +++ Lazy.List.map2 (,) (shrinkA a) (shrinkB b) 281 | 282 | 283 | {-| 3-Tuple shrinker constructor. 284 | Takes a tuple of shrinkers and returns a shrinker of tuples. 285 | -} 286 | tuple3 : ( Shrinker a, Shrinker b, Shrinker c ) -> Shrinker ( a, b, c ) 287 | tuple3 ( shrinkA, shrinkB, shrinkC ) ( a, b, c ) = 288 | Lazy.List.map (\c -> ( a, b, c )) (shrinkC c) 289 | +++ Lazy.List.map (\b -> ( a, b, c )) (shrinkB b) 290 | +++ Lazy.List.map (\a -> ( a, b, c )) (shrinkA a) 291 | +++ Lazy.List.map2 (\b c -> ( a, b, c )) (shrinkB b) (shrinkC c) 292 | +++ Lazy.List.map2 (\a c -> ( a, b, c )) (shrinkA a) (shrinkC c) 293 | +++ Lazy.List.map2 (\a b -> ( a, b, c )) (shrinkA a) (shrinkB b) 294 | +++ Lazy.List.map3 (,,) (shrinkA a) (shrinkB b) (shrinkC c) 295 | 296 | 297 | {-| 4-Tuple shrinker constructor. 298 | Takes a tuple of shrinkers and returns a shrinker of tuples. 299 | -} 300 | tuple4 : ( Shrinker a, Shrinker b, Shrinker c, Shrinker d ) -> Shrinker ( a, b, c, d ) 301 | tuple4 ( shrinkA, shrinkB, shrinkC, shrinkD ) ( a, b, c, d ) = 302 | Lazy.List.map (\d -> ( a, b, c, d )) (shrinkD d) 303 | +++ Lazy.List.map (\c -> ( a, b, c, d )) (shrinkC c) 304 | +++ Lazy.List.map (\b -> ( a, b, c, d )) (shrinkB b) 305 | +++ Lazy.List.map (\a -> ( a, b, c, d )) (shrinkA a) 306 | +++ Lazy.List.map2 (\c d -> ( a, b, c, d )) (shrinkC c) (shrinkD d) 307 | +++ Lazy.List.map2 (\b d -> ( a, b, c, d )) (shrinkB b) (shrinkD d) 308 | +++ Lazy.List.map2 (\a d -> ( a, b, c, d )) (shrinkA a) (shrinkD d) 309 | +++ Lazy.List.map2 (\b c -> ( a, b, c, d )) (shrinkB b) (shrinkC c) 310 | +++ Lazy.List.map2 (\a c -> ( a, b, c, d )) (shrinkA a) (shrinkC c) 311 | +++ Lazy.List.map2 (\a b -> ( a, b, c, d )) (shrinkA a) (shrinkB b) 312 | +++ Lazy.List.map3 (\b c d -> ( a, b, c, d )) (shrinkB b) (shrinkC c) (shrinkD d) 313 | +++ Lazy.List.map3 (\a c d -> ( a, b, c, d )) (shrinkA a) (shrinkC c) (shrinkD d) 314 | +++ Lazy.List.map3 (\a b d -> ( a, b, c, d )) (shrinkA a) (shrinkB b) (shrinkD d) 315 | +++ Lazy.List.map3 (\a b c -> ( a, b, c, d )) (shrinkA a) (shrinkB b) (shrinkC c) 316 | +++ Lazy.List.map4 (,,,) (shrinkA a) (shrinkB b) (shrinkC c) (shrinkD d) 317 | 318 | 319 | {-| 5-Tuple shrinker constructor. 320 | Takes a tuple of shrinkers and returns a shrinker of tuples. 321 | -} 322 | tuple5 : ( Shrinker a, Shrinker b, Shrinker c, Shrinker d, Shrinker e ) -> Shrinker ( a, b, c, d, e ) 323 | tuple5 ( shrinkA, shrinkB, shrinkC, shrinkD, shrinkE ) ( a, b, c, d, e ) = 324 | Lazy.List.map (\e -> ( a, b, c, d, e )) (shrinkE e) 325 | +++ Lazy.List.map (\d -> ( a, b, c, d, e )) (shrinkD d) 326 | +++ Lazy.List.map (\c -> ( a, b, c, d, e )) (shrinkC c) 327 | +++ Lazy.List.map (\b -> ( a, b, c, d, e )) (shrinkB b) 328 | +++ Lazy.List.map (\a -> ( a, b, c, d, e )) (shrinkA a) 329 | +++ Lazy.List.map2 (\d e -> ( a, b, c, d, e )) (shrinkD d) (shrinkE e) 330 | +++ Lazy.List.map2 (\c e -> ( a, b, c, d, e )) (shrinkC c) (shrinkE e) 331 | +++ Lazy.List.map2 (\b e -> ( a, b, c, d, e )) (shrinkB b) (shrinkE e) 332 | +++ Lazy.List.map2 (\a e -> ( a, b, c, d, e )) (shrinkA a) (shrinkE e) 333 | +++ Lazy.List.map2 (\c d -> ( a, b, c, d, e )) (shrinkC c) (shrinkD d) 334 | +++ Lazy.List.map2 (\b d -> ( a, b, c, d, e )) (shrinkB b) (shrinkD d) 335 | +++ Lazy.List.map2 (\a d -> ( a, b, c, d, e )) (shrinkA a) (shrinkD d) 336 | +++ Lazy.List.map2 (\b c -> ( a, b, c, d, e )) (shrinkB b) (shrinkC c) 337 | +++ Lazy.List.map2 (\a c -> ( a, b, c, d, e )) (shrinkA a) (shrinkC c) 338 | +++ Lazy.List.map2 (\a b -> ( a, b, c, d, e )) (shrinkA a) (shrinkB b) 339 | +++ Lazy.List.map3 (\a b c -> ( a, b, c, d, e )) (shrinkA a) (shrinkB b) (shrinkC c) 340 | +++ Lazy.List.map3 (\a b d -> ( a, b, c, d, e )) (shrinkA a) (shrinkB b) (shrinkD d) 341 | +++ Lazy.List.map3 (\a c d -> ( a, b, c, d, e )) (shrinkA a) (shrinkC c) (shrinkD d) 342 | +++ Lazy.List.map3 (\b c d -> ( a, b, c, d, e )) (shrinkB b) (shrinkC c) (shrinkD d) 343 | +++ Lazy.List.map3 (\a b e -> ( a, b, c, d, e )) (shrinkA a) (shrinkB b) (shrinkE e) 344 | +++ Lazy.List.map3 (\a c e -> ( a, b, c, d, e )) (shrinkA a) (shrinkC c) (shrinkE e) 345 | +++ Lazy.List.map3 (\b c e -> ( a, b, c, d, e )) (shrinkB b) (shrinkC c) (shrinkE e) 346 | +++ Lazy.List.map3 (\a d e -> ( a, b, c, d, e )) (shrinkA a) (shrinkD d) (shrinkE e) 347 | +++ Lazy.List.map3 (\b d e -> ( a, b, c, d, e )) (shrinkB b) (shrinkD d) (shrinkE e) 348 | +++ Lazy.List.map3 (\c d e -> ( a, b, c, d, e )) (shrinkC c) (shrinkD d) (shrinkE e) 349 | +++ Lazy.List.map4 (\b c d e -> ( a, b, c, d, e )) (shrinkB b) (shrinkC c) (shrinkD d) (shrinkE e) 350 | +++ Lazy.List.map4 (\a c d e -> ( a, b, c, d, e )) (shrinkA a) (shrinkC c) (shrinkD d) (shrinkE e) 351 | +++ Lazy.List.map4 (\a b d e -> ( a, b, c, d, e )) (shrinkA a) (shrinkB b) (shrinkD d) (shrinkE e) 352 | +++ Lazy.List.map4 (\a b c d -> ( a, b, c, d, e )) (shrinkA a) (shrinkB b) (shrinkC c) (shrinkD d) 353 | +++ Lazy.List.map5 (,,,,) (shrinkA a) (shrinkB b) (shrinkC c) (shrinkD d) (shrinkE e) 354 | 355 | 356 | 357 | ---------------------- 358 | -- HELPER FUNCTIONS -- 359 | ---------------------- 360 | 361 | 362 | {-| Convert a Shrinker of a's into a Shrinker of b's using two inverse functions. 363 | 364 | If you use this function as follows: 365 | 366 | shrinkerB = f g shrinkerA 367 | 368 | Make sure that 369 | 370 | `f(g(x)) == x` for all x 371 | 372 | Or else this process will generate garbage. 373 | -} 374 | convert : (a -> b) -> (b -> a) -> Shrinker a -> Shrinker b 375 | convert f g shrink b = 376 | Lazy.List.map f (shrink (g b)) 377 | 378 | 379 | {-| Filter out the results of a shrinker. The resulting shrinker 380 | will only produce shrinks which satisfy the given predicate. 381 | -} 382 | keepIf : (a -> Bool) -> Shrinker a -> Shrinker a 383 | keepIf predicate shrink a = 384 | Lazy.List.keepIf predicate (shrink a) 385 | 386 | 387 | {-| Filter out the results of a shrinker. The resulting shrinker 388 | will only throw away shrinks which satisfy the given predicate. 389 | -} 390 | dropIf : (a -> Bool) -> Shrinker a -> Shrinker a 391 | dropIf predicate = 392 | keepIf (not << predicate) 393 | 394 | 395 | {-| Merge two shrinkers. Generates all the values in the first 396 | shrinker, and then all the non-duplicated values in the second 397 | shrinker. 398 | -} 399 | merge : Shrinker a -> Shrinker a -> Shrinker a 400 | merge shrink1 shrink2 a = 401 | Lazy.List.unique (shrink1 a +++ shrink2 a) 402 | 403 | 404 | {-| Re-export of `Lazy.List.map` 405 | This is useful in order to compose shrinkers, especially when used in 406 | conjunction with `andMap`. For example: 407 | 408 | type alias Vector = 409 | { x : Float 410 | , y : Float 411 | , z : Float 412 | } 413 | 414 | vector : Shrinker Vector 415 | vector {x,y,z} = 416 | Vector 417 | `map` float x 418 | `andMap` float y 419 | `andMap` float z 420 | -} 421 | map : (a -> b) -> LazyList a -> LazyList b 422 | map = 423 | Lazy.List.map 424 | 425 | 426 | {-| Apply a lazy list of functions on a lazy list of values. 427 | 428 | The argument order is so that it is easy to use in `|>` chains. 429 | -} 430 | andMap : LazyList a -> LazyList (a -> b) -> LazyList b 431 | andMap = 432 | Lazy.List.andMap 433 | 434 | 435 | 436 | ----------------------- 437 | -- PRIVATE FUNCTIONS -- 438 | ----------------------- 439 | 440 | 441 | seriesInt : Int -> Int -> LazyList Int 442 | seriesInt low high = 443 | if low >= high then 444 | empty 445 | else if low == high - 1 then 446 | low ::: empty 447 | else 448 | let 449 | low_ = 450 | low + ((high - low) // 2) 451 | in 452 | low ::: seriesInt low_ high 453 | 454 | 455 | seriesFloat : Float -> Float -> LazyList Float 456 | seriesFloat low high = 457 | if low >= high - 0.0001 then 458 | if high /= 0.000001 then 459 | Lazy.List.singleton (low + 0.000001) 460 | else 461 | empty 462 | else 463 | let 464 | low_ = 465 | low + ((high - low) / 2) 466 | in 467 | low ::: seriesFloat low_ high 468 | --------------------------------------------------------------------------------