ppx_enumerate

November 21, 2024 ยท View on GitHub

Generate a list containing all values of a finite type.

ppx_enumerate is a ppx rewriter which generates a definition for the list of all values of a type with (for a type which only has finitely many values).

Basic Usage

The basic usage is simply to add "[@@deriving enumerate]" after the type definition. For example:

type t =
  | Foo
  | Bar of bool
  | Baz of [`A | `B of unit option]
  [@@deriving enumerate]

will produce a value val all : t list, whose value is equal to

[ Foo; Bar true; Bar false; Baz `A; Baz (`B None); Baz (`B Some ()) ]

in some order (that is, there is no guarantee about the order of the list).

Polymorphic types

In a similar fashion as sexplib, using '[@@deriving enumerate]' on polymorphic types produces a function for [all]. For example,

type 'a t =
  | Foo
  | Bar of 'a option
  [@@deriving enumerate]

will produce a value val all : 'a list -> 'a t list, whose value is semantically equal to

fun all_of_a -> Foo :: Bar None :: List.map all_of_a ~f:(fun x -> Bar (Some x))

Types not named t

If the type is not named t, then the enumeration is called all_of_<type_name> instead of all.

Records and Tuples

Product types are supported as well as sum types. For example,

type t =
  { foo : [`A | `B]
  ; bar : [`C | `D]
  } [@@deriving enumerate]

produces a val all : t list whose value is equal (up to order) to:

[ { foo = `A; bar = `C }; { foo = `A; bar = `D };
  { foo = `B; bar = `C }; { foo = `B; bar = `D };
]

Tuples and variants with multiple arguments are similarly supported.

Overriding the all value

Just like with sexplib, it can sometimes be useful to provide a custom value of all. For example, you might define a type of bounded integers:

module Small_int : sig
  type t = private int [@@deriving enumerate]
  val create_exn : int -> t
end = struct
  type t = int
  let limit = 100
  let create_exn i = if i < 0 || i >= limit then failwith "out of bounds"; i
  let all = List.init limit ~f:(fun i -> i)
end

You could then use Small_int.t as normal with other types using [@@deriving enumerate]:

type t =
  | Foo
  | Bar of Small_int.t option
  [@@deriving enumerate]

Custom attribute

You can also override all for a type within a type definition using the [@enumerate.custom] attribute:

type t =
  | Foo
  | Bar of (string[@enumerate.custom [ "baz"; "qux" ]]) * bool

This can be useful in cases where you want to use enumerate to help exercise different codepaths based on which case you're in, but the contents are not enumerable. For example, your code may have different control flow based on whether a string option is Some str or None, but not have material changes based on the value of str, in which case you could use (string [@enumerate.custom [ "example" ]]) option.

Using all without defining a type name

You don't have to define a type name to be able to create the list of values of a type. You do it for any type expression by using the all quotation. For example:

[%all: bool * bool]

which will evaluate to:

[ (true, true); (true, false); (false, false); (false, true) ]

Known issues

Using all for polymorphic variants with duplicated constructors leads to duplicate values in the resulting lists:

type t = [ `A ] [@@deriving enumerate]
let () = assert ([%all: [ t | t ] ] = [ `A; `A ])