package ppx_compare
Install
Dune Dependency
Authors
Maintainers
Sources
sha256=f0b23eb78082ef4dc71a66939bbc63c6b0cc2cf6a4744a906b7a2c016cbe3098
Description
Part of the Jane Street's PPX rewriters collection.
Published: 23 May 2024
README
ppx_compare
Generation of fast comparison and equality functions from type expressions and definitions.
Ppx_compare is a ppx rewriter that derives comparison and equality functions from type representations. The scaffolded functions are usually much faster than ocaml's Pervasives.compare
and Pervasives.(=)
. Scaffolding functions also gives you more flexibility by allowing you to override them for a specific type and more safety by making sure that you only compare comparable values.
Syntax
Type definitions: [@@deriving compare, equal]
Expressions: [%compare: TYPE]
, [%equal: TYPE]
and [%compare.equal: TYPE]
Types, record fields: [@compare.ignore]
, [@equal.ignore]
Basic usage
We use ppx_deriving
/ppx_type_conv
, so type definitions are annotated this way:
type s = v * w [@@deriving compare]
This will generate compare_s : s -> s -> int
function that relies on compare_v : v -> v -> int
and compare_w : w -> w -> int
.
Compare is not DWIM (do what I mean): it will scaffold a fast well behaved comparison (reflexive, transitive, symmetric...) function however it does not try to follow any "natural ordering". For instance arrays of characters are not sorted lexicographically.
Base types (options,int,array,lists,char,floats...) have the same comparison order as Pervasives.compare (provided their type parameters also do for the polymorphic ones). Comparisons for these types must be brought in scope with open Base
, open Core
, or open Ppx_compare_lib.Builtin
.
Records fields are compared in the order they are defined (left to right); tuples fields are compared left to right. When we compare two branches of a sum whichever ones comes first in the definition is considered lowest. Variants compare in the order they are listed (increasing top-to-bottom). Polymorphic variants use the same ordering as the ocaml runtime.
The same applies to equality functions.
Float equality
The functions derived by [@@deriving equal]
are consistent with the compare functions derived by [@@deriving compare]
and in particular do not respect IEEE float comparison.
Calling compare
for type t
s
In compliance (or conformance) with Janestreet's coding standard we assume that type named t
are the main types in a module and
type t = S.t * T.t [@@deriving compare]
will call the functions S.compare
and T.compare
instead of calling S.compare_t
and T.compare_t
. This will also generate a compare : t -> t -> int
function.
The same applies to equality functions.
Signature
type t [@@deriving compare]
in a module signature will add val compare : t -> t -> int
in the signature.
The same applies to equality functions.
Comparison without a type definition
Sometimes you just want a comparison without having to create a new type. You can create such a comparison function using the [%compare: ..]
extension point:
let gt x y = [%compare: float * int * [`A | `B | `C] ] x y
You can use the type _
, in which case the corresponding values will be ignored (i.e. compared using fun _ _ -> 0
). For instance:
assert ([%compare: _ list] [ true ] [ false ] = 0);
assert ([%compare: _ list] [] [ false ] <> 0);
The same applies to equality functions.
You can also check for equality using [%compare.equal: ..]
, which produces a function that returns true
precisely when [%compare: ..]
returns 0
. [%equal: ..]
is preferred over [%compare.equal: ..]
and in particular is expected to be slightly faster. However, [%compare.equal: ..]
can come in handy for types that only have [@@deriving compare]
. In particular, support for [@@deriving equal]
was added long after the project started, which means that many types out there only support [@deriving compare]
.
Ignoring part of types
The comparison ignores any part of the type declaration that is under a [@compare.ignore]
annotation:
type t = (float [@compare.ignore]) * string
[@@deriving compare]
The same applies for [@@deriving equal]
by using [@equal.ignore]
. In order to ignore part of a type for both comparison and equality, you can simply use [@ignore]
. However, be aware that the general [@ignore]
attribute will apply to any deriver that recognize it, not just compare
and equal
.
Note that if you use both the compare
and equal
derivers, you need to use either both [@compare.ignore]
and [@equal.ignore]
or [@ignore]
. However, you cannot use only one of them.
For convenience, you can also ignore record fields instead of the type of record field. In other words,
type t =
{ a : (float [@compare.ignore])
; b : string
}
[@@deriving compare]
can be abbreviated:
type t =
{ a : float [@compare.ignore]
; b : string
}
[@@deriving compare]
Local-accepting compare functions
This ppx includes the option to support local allocation, a nonstandard OCaml extension available at: https://github.com/ocaml-flambda/ocaml-jst
In both structures and signatures, [@@deriving compare ~localize]
(and similarly for equal
) generates definitions with the following types, in addition to the usual definitions:
(* Monomorphic types *)
val compare__local : local_ t -> local_ t -> int
val equal__local : local_ t -> local_ t -> bool
(* Parameterized types *)
val compare__local
: (local_ 'a -> local_ 'a -> int)
-> local_ 'a t
-> local_ 'a t
-> int
val equal__local
: (local_ 'a -> local_ 'a -> bool)
-> local_ 'a t
-> local_ 'a t
-> bool
You can also use the [%compare_local: _]
, [%equal_local: _]
and [%compare_local.equal: _]
extension points to generate the corresponding types and functions.
For types named something other than t
, the naming pattern is similar to the non-local versions:
type foo
val compare_foo__local : local_ foo -> local_ foo -> int
Dependencies (5)
-
ppxlib
>= "0.28.0"
-
dune
>= "3.11.0"
-
ppxlib_jane
>= "v0.17" & < "v0.18"
-
base
>= "v0.17" & < "v0.18"
-
ocaml
>= "5.1.0"
Dev Dependencies
None
Used by (35)
-
bin_prot
>= "v0.17.0"
-
bio_io
>= "0.5.1"
-
biocaml
>= "0.11.0"
- camlix
-
clangml
= "4.0.0"
- cmdlang-to-base
-
cohttp
= "3.0.0"
-
coq-lsp
>= "0.2.0+8.17"
-
coq-serapi
>= "8.16.0+0.16.0"
-
core_bench
>= "v0.17.0"
-
frenetic
>= "5.0.0" & < "5.0.5"
- h1_parser
- http
-
inferno
>= "20220603"
-
little_logger
< "0.2.0"
- m_tree
-
n_ary
>= "v0.17.0"
-
nsq
>= "0.2.5" & < "0.5.2"
- oci
- opine
-
override
>= "0.2.0" & < "0.2.2"
-
pgx
>= "1.0"
-
ppx_assert
>= "v0.17.0"
-
ppx_bap
< "v0.14.0"
-
ppx_base
>= "v0.17.0"
- ppx_diff
-
ppx_hash
>= "v0.17.0"
-
ppx_log
>= "v0.17.0"
-
ppx_typed_fields
>= "v0.17.0"
- prettiest
-
provider
< "0.0.9"
- pyre-ast
-
SZXX
>= "4.1.0"
-
sexp_grammar
>= "v0.17.0"
-
string_dict
>= "v0.17.0"
Conflicts
None