edkelly303/elm-exhaustive-generators/1.0.0

Exhaustive

Generators and testing

type alias Generator value =
{ count : Int
, nth : Int -> Maybe value
, all : () -> List value
, every : Int -> List value
, sample : Float -> List value
}

A Generator provides a set of functions that are useful when generating values of a given type.

The functions are:

count : Int

The total number of values that will be produced by the generator

import Exhaustive exposing (bool)


bool.count


--> 2

nth : Int -> Maybe value

Generate the nth value in the sequence. Sequences begin at n = 0. This function returns Nothing if you pass an integer that is greater than count.

import Exhaustive exposing (bool)


bool.nth 0


--> Just False

all : () -> List value

Generate a list of all the values that the generator can produce, starting with n = 0 and ending with n = count. To avoid creating huge lists unnecessarily, you need to pass a () to this function to generate the list.

import Exhaustive exposing (bool)


bool.all ()


--> [ False, True ]

every : Int -> List value

Generate a list of a subset of values, starting with the value at n = 0 and then taking values at a specified interval.

import Exhaustive exposing (values)


zeroToNine =
    values (List.range 0 9)


zeroToNine.every 2


--> [ 0, 2, 4, 6, 8 ]

sample : Float -> List value

Generate a list of an evenly distributed percentage of the values that the generator can produce, starting with the value at n = 0. The percentage should be specified as a Float between 0.0 and 1.0.

import Exhaustive exposing (values)


zeroToNine =
    values (List.range 0 9)


zeroToNine.sample 0.25


--> [ 0, 4, 8 ]
test : Generator value -> String -> (value -> Expectation) -> Test

Turn a Generator into a Test, for use with elm-test.

The API is similar to elm-test's Test.fuzz, except that instead of supplying a fuzzer, you supply a Generator from this package.

When the test is executed, elm-test will check the expectation against every term in the sequence, starting from the 0th term and continuing until either an expectation fails, or the generator is exhausted.

import Exhaustive exposing (test, int)
import Test
import Expect




test int "This integer is never zero" <|
    \n -> n |> Expect.notEqual 0


--: Test.Test

Generators for primitive types

unit : Generator ()

Generate all values of the unit type, (). There's only one!

import Exhaustive exposing (unit)


unit.nth 0


--> Just ()


unit.nth 1


--> Nothing
bool : Generator Bool

Generate all boolean values. The 0th term is False, the 1st term is True.

import Exhaustive exposing (bool)


bool.all ()


--> [ False, True ]
int : Generator Int

Generate integer values.

Since the set of all integers is infinite, we don't attempt to generate all of them. Instead, we try a small set of potentially interesting integers, which may be useful in tests:

import Exhaustive exposing (int)


int.all ()


--> [ 0, -1, 1, 2, 3, -10, 10, -100, 100 ]

(I am open to suggestions for more interesting integers than these!)

If you want to generate a specific list of integers, see the docs for values instead.

float : Generator Float

Generate Float values.

Since the set of all floats is infinite, we don't attempt to generate all of them. Instead, we try a small set of potentially interesting floats, which may be useful in tests:

import Exhaustive exposing (float)


float.nth 0


--> Just 0.0

(I am open to suggestions for more interesting floats than these!)

If you want to generate a specific list of floats, see the docs for values instead.

char : Generator Char

Generate Char values.

Since the set of all Chars is infinite, we don't attempt to generate all of them. Instead, we try a small set of potentially interesting Chars, which may be useful in tests:

import Exhaustive exposing (char)


char.nth 0


--> Just ' '

(I am open to suggestions for more interesting Chars than these!)

If you want to generate a specific list of Chars, see the docs for values instead.

string : Generator String

Generate String values.

Since the set of all Strings is infinite, we don't attempt to generate all of them. Instead, we try a small set of potentially interesting Strings, which may be useful in tests:

import Exhaustive exposing (string)


string.nth 0


--> Just ""

(I am open to suggestions for more interesting Strings than these!)

If you want to generate a specific list of Strings, see the docs for values instead.

Generators for complex types

Built-in combinators

Generator value1
-> Generator value2
-> Generator ( value1, value2 )

Generate all pairs of values of given types.

import Exhaustive exposing (pair, bool)


pairBool =
    pair bool bool


pairBool.all ()


--> [ ( False, False ), ( True, False ), ( False, True ), ( True, True ) ]
Generator value1
-> Generator value2
-> Generator value3
-> Generator ( value1, value2, value3 )

Generate all triples of values of given types.

import Exhaustive exposing (triple, bool, unit)


tripleBoolUnitUnit =
    triple bool unit unit


tripleBoolUnitUnit.all ()


--> [ ( False, (), () ), ( True, (), () ) ]
maybe : Generator value -> Generator (Maybe value)

Generate all Maybe values of a given type.

import Exhaustive exposing (maybe, bool)


maybeBool =
    maybe bool


maybeBool.all ()


--> [ Nothing, Just False, Just True ]
Generator error
-> Generator value
-> Generator (Result error value)

Generate all Result values of given error and ok types.

import Exhaustive exposing (result, unit, bool)


resultUnitBool =
    result unit bool


resultUnitBool.all ()


--> [ Err (), Ok False, Ok True ]
list : Int -> Generator value -> Generator (List value)

Generate all lists of values of a given type, starting with the empty list [], and continuing up to lists of a specified length.

import Exhaustive exposing (list, bool)


listBool =
    list 1 bool


listBool.all ()


--> [ [], [ False ], [ True ] ]
Int
-> Generator comparable
-> Generator value
-> Generator (Dict comparable value)

Generate all Dicts of a given key and value type, starting with an empty Dict and continuing up to Dicts of a specified length.

import Exhaustive exposing (dict, values, unit)
import Dict


myDict =
    dict 1
        (values [ "a", "b" ])
        unit


myDict.all ()


--> [ Dict.fromList [], Dict.fromList [ ( "a", () ) ], Dict.fromList [ ( "b", () ) ] ]
array : Int -> Generator value -> Generator (Array value)

Generate all Arrays of a given type, starting with an empty Array and continuing up to Arrays of a specified length.

import Exhaustive exposing (array, bool)
import Array


arrayBool =
    array 1 bool


arrayBool.all ()


--> [ Array.fromList [], Array.fromList [ False ], Array.fromList [ True ] ]

Records

record : value -> Generator value

Generate all values of a record type. Use with field.

import Exhaustive exposing (record, field, bool, unit)


rec =
    record (\foo bar -> { foo = foo, bar = bar })
        |> field bool
        |> field unit


rec.all ()


--> [ { foo = False, bar = () }, { foo = True, bar = () } ]
field : Generator field -> Generator (field -> record) -> Generator record

Specify the contents of a field for a record type. Use with record.

Custom types

customType : Generator value

Generate all values of a custom type . Use with variant0, variant1, etc.

import Exhaustive exposing (customType, variant0, variant1, variant2, bool, unit)


type Foo
    = Foo Bool
    | Bar () Bool
    | Baz


foo =
    customType
        |> variant1 Foo bool
        |> variant2 Bar unit bool
        |> variant0 Baz


foo.all ()


--> [ Foo False, Foo True, Bar () False, Bar () True, Baz ]
variant0 : variant -> Generator variant -> Generator variant

Specify a custom type variant with no arguments. Use with customType.

(arg -> variant)
-> Generator arg
-> Generator variant
-> Generator variant

Specify a custom type variant with one argument. Use with customType.

(arg1 -> arg2 -> variant)
-> Generator arg1
-> Generator arg2
-> Generator variant
-> Generator variant

Specify a custom type variant with two arguments. Use with customType.

(arg1 -> arg2 -> arg3 -> variant)
-> Generator arg1
-> Generator arg2
-> Generator arg3
-> Generator variant
-> Generator variant

Specify a custom type variant with three arguments. Use with customType.

(arg1 -> arg2 -> arg3 -> arg4 -> variant)
-> Generator arg1
-> Generator arg2
-> Generator arg3
-> Generator arg4
-> Generator variant
-> Generator variant

Specify a custom type variant with four arguments. Use with customType.

(arg1 -> arg2 -> arg3 -> arg4 -> arg5 -> variant)
-> Generator arg1
-> Generator arg2
-> Generator arg3
-> Generator arg4
-> Generator arg5
-> Generator variant
-> Generator variant

Specify a custom type variant with five arguments. Use with customType.

Defining custom generators

empty : Generator value

A Generator that never produces any values.

import Exhaustive exposing (empty)


empty.nth 0


--> Nothing
constant : value -> Generator value

A Generator that produces exactly one value.

import Exhaustive exposing (constant)


one =
    constant 1


one.nth 0


--> Just 1


one.nth 1


--> Nothing
values : List value -> Generator value

A Generator that produces a specific sequence of values.

import Exhaustive exposing (values)


abc =
    values [ "a", "b", "c" ]


abc.nth 2


--> Just "c"


abc.nth 3


--> Nothing
new : (Int -> Maybe value) -> Generator value

Define a new generator by providing an implementation for generating the nth term.

import Exhaustive exposing (new)


type Colour
    = Red
    | Green
    | Blue


colour =
    new
        (\n ->
            case n of
                0 -> Just Red
                1 -> Just Green
                2 -> Just Blue
                _ -> Nothing
        )


colour.all ()


--> [ Red, Green, Blue ]

(This is a silly example. If you really had a Colour type like this, it would be better to use the customType and variant0 functions.)

Transforming, chaining and combining generators

map : (value1 -> value2) -> Generator value1 -> Generator value2

Convert a Generator of one type of value into a Generator of another type of value.

import Exhaustive exposing (map, int)


type Id
    = Id Int


id =
    map Id int


id.nth 0


--> Just (Id 0)
andThen : (value1 -> Generator value2) -> Generator value1 -> Generator value2

Create a Generator for values of one type based on the values produced by a generator of another type.

import Exhaustive exposing (andThen, bool, constant, values)


oneTwoThree =
    bool
        |> andThen
            (\b ->
                case b of
                    False ->
                        values [ 1, 2 ]


                    True ->
                        constant 3
            )


oneTwoThree.all ()


--> [ 1, 2, 3 ]
append : Generator value -> Generator value -> Generator value

Combine two Generators of the same type. This will first generate all the values from the first generator, and then all the values from the second generator.

import Exhaustive exposing (append, values)


oneTwoThreeFour =
    append (values [ 1, 2 ]) (values [ 3, 4 ])


oneTwoThreeFour.all ()


--> [ 1, 2, 3, 4 ]