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 ])