metamon/generator

Generators: deterministic, size-aware samplers that produce values alongside their lazy shrink alternatives.

A Generator(a) is a pair of:

Combinators (map, bind, map2, tuple2, one_of, …) preserve integrated shrinking via the underlying Tree(a) and propagate edges in a way appropriate for each combinator (see § 4.4 of the spec).

Types

A generator of a. Use the constructors in this module rather than pattern-matching on the opaque structure.

pub opaque type Generator(a)

Values

pub fn add_edges(g: Generator(a), more: List(a)) -> Generator(a)

Append more edges to an existing generator. Symmetric to with_examples but reads better for library authors.

pub fn ascii_alphanumeric() -> Generator(String)

A single ASCII alphanumeric character.

pub fn ascii_digit() -> Generator(String)

A single ASCII digit (0-9).

pub fn ascii_letter() -> Generator(String)

A single ASCII letter generator (a-zA-Z).

pub fn ascii_lower() -> Generator(String)

A single ASCII lowercase letter generator (a-z).

pub fn ascii_printable() -> Generator(String)

A single ASCII printable character (space..~).

pub fn ascii_upper() -> Generator(String)

A single ASCII uppercase letter generator (A-Z).

pub fn bind(
  g: Generator(a),
  k: fn(a) -> Generator(b),
) -> Generator(b)

Monadic bind. Edges are taken from the outer generator only (the inner edges depend on the value, which is not safe to enumerate at composition time without running the inner generator with a fixed seed).

pub fn bit_array(byte_len: range.Range) -> Generator(BitArray)

Bit array whose total length in bytes lies inside byte_len. Each byte is generated uniformly. Output is always byte-aligned (no sub-byte tail).

pub fn bit_array_printable(
  byte_len: range.Range,
) -> Generator(BitArray)

Bit array whose every byte is a printable ASCII codepoint (0x20..0x7E). byte_len is, like bit_array, the number of bytes in the resulting array. Useful for fuzzing parsers that take BitArray but expect printable input (HTTP headers, MIME types, etc.).

pub fn bit_array_unaligned(
  bit_len: range.Range,
) -> Generator(BitArray)

Bit array of arbitrary bit length, possibly NOT byte-aligned. bit_len is the total number of bits in the result.

Use this generator to fuzz code paths that take a BitArray whose total length is not necessarily a multiple of 8: codec internals, length-prefixed framing, bignum bit walks, and any parser that must reject (or handle) sub-byte tails. Reach for bit_array when byte-aligned input is sufficient — it is faster and the size metric matches byte-oriented properties more naturally.

pub fn bit_array_utf8(
  codepoint_len: range.Range,
) -> Generator(BitArray)

Bit array that is guaranteed to be valid UTF-8. codepoint_len is the number of Unicode codepoints; the resulting byte length will be larger when the random string contains multi-byte codepoints.

pub fn bool() -> Generator(Bool)

True or False, uniformly.

pub fn byte() -> Generator(Int)

A single byte ([0, 255]).

pub fn dict_of(
  key: Generator(k),
  value: Generator(v),
  len: range.Range,
) -> Generator(dict.Dict(k, v))

Generate a dict.Dict(k, v) of size in len. Duplicate keys are resolved by last-write-wins (the standard dict.from_list semantics).

pub fn edges_of(g: Generator(a)) -> List(a)

Inspect the must-try edge values associated with a generator.

pub fn element_of(values: List(a)) -> Generator(a)

Pick uniformly from a non-empty list of values. Equivalent to one_of(list.map(values, return)) and inherits the same edge behaviour: every value is an edge.

pub fn filter(
  g: Generator(a),
  predicate: fn(a) -> Bool,
) -> Generator(a)

Reject values that fail predicate. The implementation retries internally up to filter_retry_limit times before panicking; this is the canonical “filter is too strict” failure and is preferable to silently misreporting the property.

Edges are filtered by predicate so user-supplied edges that fail the predicate are silently dropped (they would never be tried anyway).

pub fn float(lo: Float, hi: Float) -> Generator(Float)

Generates finite Float values uniformly in the closed interval [lo, hi]. Shrinking moves toward the closest endpoint (no fancy mantissa shrinking yet).

This generator does not emit NaN, ±Infinity, or denormal values. Use float_special (or splice float_special_edges() via with_examples) when codec correctness depends on those edges — f64.to_string(NaN) formats as "NaN" but a parser may not accept the token, -0.0 round-trips differently than 0.0 through some encoders, etc.

pub fn float_special() -> Generator(Float)

IEEE 754 special-value generator: emits values that the regular float generator never produces — NaN, +Infinity, -Infinity, the smallest positive denormal, the largest finite double, plus the “ordinary” anchors 0.0, -0.0, 1.0. Every value is also an edge, so the runner tries each one before falling back to random.

Use this when codec / serialisation correctness depends on IEEE edges (f64.to_string(NaN) formats as "NaN", but a parser may not accept that token; -0.0 round-trips differently than 0.0 through some encoders, etc.). The values pass through metamon’s edge pipeline unchanged because the runner does not perform arithmetic on edge values — but downstream user code that does arithmetic on the generated value will raise badarith on the BEAM, exactly as IEEE 754 expects.

Target asymmetry: on JavaScript, the non-finite slots return genuine NaN / ±Infinity. On the BEAM, they return finite sentinels (largest finite double for NaN / +Infinity, the negation thereof for -Infinity) because the BEAM has no portable way to construct a non-finite double from pure Erlang. Properties that strictly require genuine non-finite values must run on the JavaScript target.

pub fn float_special_edges() -> List(Float)

The list of values emitted by float_special. Exposed so callers can splice them into a custom range generator via with_examples(my_float_gen, generator.float_special_edges()).

pub fn frequency(
  weighted: List(#(Int, Generator(a))),
) -> Generator(a)

Weighted choice over a non-empty list of (weight, generator) pairs.

Weights must be >= 1. A weight of 0 or a negative weight is a programming error (a “disabled” branch silently re-enabled by the previous coercion to 1, or a weight = max(0, computed) defensive pattern losing its safety net) and panics with a structured message naming the offending position. Pass at least 1 for any branch you want to keep, or remove the entry entirely to opt it out.

pub fn generate(
  g: Generator(a),
  s: seed.Seed,
  size: Int,
) -> tree.Tree(a)

Run the generator at the given seed and size. Used by the runner; most user code should not call this directly.

pub fn int(range_value: range.Range) -> Generator(Int)

Integer in the given range, with binary shrinking toward the range origin. Standard edges (0, 1, -1, bounds) are populated within the constant interval.

pub fn list_of(
  element: Generator(a),
  len: range.Range,
) -> Generator(List(a))

A list of values whose length is drawn from len. Shrinking removes elements via shrink/list_drops and shrinks each remaining element.

pub fn map(g: Generator(a), f: fn(a) -> b) -> Generator(b)

Functor map. Edges are mapped element-wise.

pub fn map2(
  g1: Generator(a),
  g2: Generator(b),
  f: fn(a, b) -> c,
) -> Generator(c)

Applicative map over two independent generators. Shrinking holds one component fixed while shrinking the other (via tree.zip).

pub fn map3(
  g1: Generator(a),
  g2: Generator(b),
  g3: Generator(c),
  f: fn(a, b, c) -> d,
) -> Generator(d)

Three-way applicative map.

pub fn map4(
  g1: Generator(a),
  g2: Generator(b),
  g3: Generator(c),
  g4: Generator(d),
  f: fn(a, b, c, d) -> e,
) -> Generator(e)

Four-way applicative map.

pub fn map5(
  g1: Generator(a),
  g2: Generator(b),
  g3: Generator(c),
  g4: Generator(d),
  g5: Generator(e),
  f: fn(a, b, c, d, e) -> g,
) -> Generator(g)

Five-way applicative map.

pub fn map6(
  g1: Generator(a),
  g2: Generator(b),
  g3: Generator(c),
  g4: Generator(d),
  g5: Generator(e),
  g6: Generator(g),
  f: fn(a, b, c, d, e, g) -> h,
) -> Generator(h)

Six-way applicative map.

pub fn map7(
  g1: Generator(a),
  g2: Generator(b),
  g3: Generator(c),
  g4: Generator(d),
  g5: Generator(e),
  g6: Generator(g),
  g7: Generator(h),
  f: fn(a, b, c, d, e, g, h) -> i,
) -> Generator(i)

Seven-way applicative map. Like map6 but accepts a seventh generator. Use this for record types with seven fields when you want to keep integrated shrinking on every component (the bind- based workaround in the README’s Limitations section shrinks shallowly).

pub fn map8(
  g1: Generator(a),
  g2: Generator(b),
  g3: Generator(c),
  g4: Generator(d),
  g5: Generator(e),
  g6: Generator(g),
  g7: Generator(h),
  g8: Generator(i),
  f: fn(a, b, c, d, e, g, h, i) -> j,
) -> Generator(j)

Eight-way applicative map. Like map7 but accepts an eighth generator. Stops at eight because nine is comfortably above every record arity in the audited workload; reach for nested map2 / bind (and accept the shallow-shrinking caveat) if you genuinely need more.

pub fn negative_int() -> Generator(Int)

Integer in [-1_000_000, -1]. Shrinks toward -1.

pub fn no_edges(g: Generator(a)) -> Generator(a)

Strip the edges from a generator. Useful when composing into a product type that has its own edge story.

pub fn non_empty_list_of(
  element: Generator(a),
  len: range.Range,
) -> Generator(List(a))

Non-empty list. Wraps list_of and rejects empty lists from edges.

pub fn non_negative_int() -> Generator(Int)

Integer in [0, 1_000_000]. Linear scaling so small values come first; shrinks toward 0.

pub fn one_of(generators: List(Generator(a))) -> Generator(a)

Pick uniformly from a non-empty list of generators. Edges are the concatenation of all branch edges.

pub fn option_of(g: Generator(a)) -> Generator(option.Option(a))

Some / None of an inner generator.

pub fn positive_int() -> Generator(Int)

Integer in [1, 1_000_000]. Shrinks toward 1.

pub fn recursive(
  base: Generator(a),
  step: fn(Generator(a)) -> Generator(a),
) -> Generator(a)

Recursive generator. At size = 0 only base is used; at higher sizes the recursive call is the result of step applied to a copy of itself with halved size.

pub fn resize(g: Generator(a), new_size: Int) -> Generator(a)

Override the size used by a generator.

pub fn result_of(
  ok: Generator(a),
  err: Generator(e),
) -> Generator(Result(a, e))

Ok / Error of two inner generators.

pub fn return(value: a) -> Generator(a)

A constant generator that always returns value and has no shrinks.

pub fn sample(g: Generator(a), n: Int) -> List(a)

Pull n plain sample values, ignoring shrink trees. Useful at the REPL or in test diagnostics.

pub fn scale(g: Generator(a), f: fn(Int) -> Int) -> Generator(a)

Transform the size given to a generator.

pub fn set_of(
  element: Generator(a),
  len: range.Range,
) -> Generator(set.Set(a))

Generate a set.Set(a) of size in len.

pub fn sized(builder: fn(Int) -> Generator(a)) -> Generator(a)

Construct a generator whose strategy depends on the current size.

pub fn statistics(
  g: Generator(a),
  n: Int,
  classify: fn(a) -> String,
) -> dict.Dict(String, Int)

Bucket n generated values via classify for distribution sanity checks.

pub fn string(
  char_gen: Generator(String),
  len: range.Range,
) -> Generator(String)

A string built from char_gen characters with length in len.

pub fn string_alpha(len: range.Range) -> Generator(String)

A string of ASCII letters (a-zA-Z) with length in len.

pub fn string_alphanumeric(len: range.Range) -> Generator(String)

A string of ASCII alphanumerics (a-zA-Z0-9) with length in len.

pub fn string_ascii(len: range.Range) -> Generator(String)

An ASCII string with length in len.

Random sampling spans the full ASCII range (0x00..0x7F), including control characters (0x00..0x1F, 0x7F). Reach for string_printable_ascii if your property cannot tolerate control bytes; this function is for fuzzing parsers / serialisers that must handle the whole 7-bit space.

Curated edges include both control bytes (\t, \n) and printable boundaries (" ", "~").

pub fn string_digit(len: range.Range) -> Generator(String)

A string of ASCII digits (0-9) with length in len.

pub fn string_printable_ascii(
  len: range.Range,
) -> Generator(String)

A string of printable ASCII codepoints (0x20..0x7E) with length in len. Differs from string_ascii by skipping the curated edge cases that include control characters (\t, \n).

pub fn string_unicode(len: range.Range) -> Generator(String)

A Unicode-aware string (includes BiDi/emoji/NUL via edges).

Produces valid UTF-8 scalar values only — see unicode_codepoint for the codepoint set. Lone surrogates and other malformed UTF-8 byte sequences are not reachable through this generator because they cannot exist inside a Gleam String. To fuzz parsers that must accept or reject malformed UTF-8 input, use bit_array(byte_len) and operate at the byte level.

No bias toward Unicode normalisation boundaries (NFC vs NFD) is built in: equivalent forms like "é" (U+00E9) and "e\u{0301}" (U+0065 U+0301) are sampled independently. Pre-compose any equivalence-class edges via with_examples if your property depends on them.

pub fn tuple2(
  g1: Generator(a),
  g2: Generator(b),
) -> Generator(#(a, b))

Pair two independent generators.

pub fn tuple3(
  g1: Generator(a),
  g2: Generator(b),
  g3: Generator(c),
) -> Generator(#(a, b, c))

Triple of independent generators.

pub fn tuple4(
  g1: Generator(a),
  g2: Generator(b),
  g3: Generator(c),
  g4: Generator(d),
) -> Generator(#(a, b, c, d))

Quadruple of independent generators.

pub fn tuple5(
  g1: Generator(a),
  g2: Generator(b),
  g3: Generator(c),
  g4: Generator(d),
  g5: Generator(e),
) -> Generator(#(a, b, c, d, e))

Quintuple of independent generators.

pub fn tuple6(
  g1: Generator(a),
  g2: Generator(b),
  g3: Generator(c),
  g4: Generator(d),
  g5: Generator(e),
  g6: Generator(g),
) -> Generator(#(a, b, c, d, e, g))

Sextuple of independent generators.

pub fn tuple7(
  g1: Generator(a),
  g2: Generator(b),
  g3: Generator(c),
  g4: Generator(d),
  g5: Generator(e),
  g6: Generator(g),
  g7: Generator(h),
) -> Generator(#(a, b, c, d, e, g, h))

Septuple of independent generators.

pub fn tuple8(
  g1: Generator(a),
  g2: Generator(b),
  g3: Generator(c),
  g4: Generator(d),
  g5: Generator(e),
  g6: Generator(g),
  g7: Generator(h),
  g8: Generator(i),
) -> Generator(#(a, b, c, d, e, g, h, i))

Octuple of independent generators.

pub fn unicode_codepoint() -> Generator(Int)

A Unicode scalar value (excluding surrogates).

Draws from [0, 0xD7FF] ∪ [0xE000, 0x10FFFF]. The surrogate range [0xD800, 0xDFFF] is intentionally excluded because Gleam strings are UTF-8: lone surrogates would not survive string.from_utf_codepoints.

To fuzz codecs / parsers that must handle malformed UTF-8 byte sequences (lone surrogates, overlong encodings, truncated continuation bytes, …), drop down to bit_array(byte_len) and exercise the parser at the byte level instead — those inputs cannot exist inside a Gleam String and so cannot reach a property whose generator is string_unicode.

pub fn with_examples(
  g: Generator(a),
  examples: List(a),
) -> Generator(a)

Add a fixed list of must-try inputs that the runner will exercise before random generation.

Search Document