gen.frequency and oneOf shrinking across alternatives #6

Closed
opened 2026-09-13 21:24:22 +09:00 by natsukium · 1 comment
Owner

Missing

gen.frequency [ [weight gen] ... ], and gen.oneOf shrinking across alternatives toward the first one.

Why

oneOf (lib/gen.nix) picks an alternative and keeps only that alternative's shrinks. Two planned features depend on cross-alternative shrinking:

  • #2 gen.optional: "present -> absent" must be the first shrink step.
  • #5 gen.recursive: depth only shrinks if a node can shrink to a leaf, i.e. to an earlier alternative.

Hedgehog's choice / frequency shrink the chosen index toward 0 first, then the inner value. nixcheck already has the pieces:

oneOf = gens: bind (int (range.constant 0 (builtins.length gens - 1))) (i: builtins.elemAt gens i);

Suggested shape

  • frequency draws an index from the cumulative weights (rng.below total), shrinks the index toward 0 via bind, then the inner value.
  • oneOf = gens: frequency (map (g: [ 1 g ]) gens).
  • Seeds are not stability-guaranteed, so the changed random stream is fine.
  • Dogfood test in tests/self.nix: a counterexample from oneOf [ a b ] where a also fails shrinks to a value of a.

Foundation for #2, #3 (edge-value mixing), #5.

## Missing `gen.frequency [ [weight gen] ... ]`, and `gen.oneOf` shrinking *across* alternatives toward the first one. ## Why `oneOf` (`lib/gen.nix`) picks an alternative and keeps only that alternative's shrinks. Two planned features depend on cross-alternative shrinking: - #2 `gen.optional`: "present -> absent" must be the first shrink step. - #5 `gen.recursive`: depth only shrinks if a node can shrink to a leaf, i.e. to an earlier alternative. Hedgehog's `choice` / `frequency` shrink the chosen index toward 0 first, then the inner value. nixcheck already has the pieces: ```nix oneOf = gens: bind (int (range.constant 0 (builtins.length gens - 1))) (i: builtins.elemAt gens i); ``` ## Suggested shape - `frequency` draws an index from the cumulative weights (`rng.below total`), shrinks the index toward 0 via `bind`, then the inner value. - `oneOf = gens: frequency (map (g: [ 1 g ]) gens)`. - Seeds are not stability-guaranteed, so the changed random stream is fine. - Dogfood test in `tests/self.nix`: a counterexample from `oneOf [ a b ]` where `a` also fails shrinks to a value of `a`. Foundation for #2, #3 (edge-value mixing), #5.
Author
Owner

Implemented in eedf4c2, pushed to main.

Implemented in eedf4c2, pushed to main.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
natsukium/nixcheck#6
No description provided.