Pre-processor extensions
- Pre-Processor eXtensions (PPXs) are macros, they write code for us.
- They save us from writing boilerplate code, so our code is shorter and (probably) more correct.
- We got a peek at some
ppxextensions when looking at how to properly define equality in the variants lecture.
Some examples of boilerplate code ppx extensions can produce include
- Print functions (
show) - Serialization (
yojson) (turning arbitrary OCaml data into strings) - Comparison (
compare,eq)
ppx_deriving
- In the variants lecture, we showed how
[@@deriving eq]added to a tree type would automatically write the code for an equality on elements of that type.- In the top loop, type
#require "ppx_deriving.eq"to load it - In dune, put
(preprocess (pps (ppx_deriving.eq))as part of the library declaration
- In the top loop, type
- For the nucleotide example we could just use the built-in
=but we could have also used appxto make it:
# type nucleotide = A | C | G | T [@@deriving eq];;
type nucleotide = A | C | G | T
val equal_nucleotide : nucleotide -> nucleotide -> bool = <fun>
- We defined our type and got the
equal_nucleotidefunction for free.
Composing deriving eq
- If we have an
xyy_equalfunction on component types,derivingcan deriveequalfor a type built from those components. For example equality on lists of nucleotides:
# type n_list = nucleotide list [@@deriving eq];;
type n_list = nucleotide list
val equal_n_list : n_list -> n_list -> bool = <fun>
# equal_n_list [A;A;A] [A;G;A];;
- : bool = false
# type n_queue = nucleotide Queue.t [@@deriving eq];;
type n_queue = nucleotide Queue.t
val equal_n_queue : n_queue -> n_queue -> bool = <fun>
- Note that in general for a component type that is the
tof a module, the name isMy_module.equalinstead ofequal_t(we will see this later)
Some other useful @@deriving macros
[@@deriving ord]is likeeqexcept it makes acomparefunction instead ofequal
# type nucleotide = A | C | G | T [@@deriving ord];;
type nucleotide = A | C | G | T
val compare_nucleotide : nucleotide -> nucleotide -> int = fun
# compare_nucleotide A C;;
- : int = -1
# compare_nucleotide C A;;
- : int = 1
Note that existing types such as int, string, etc come with Int.compare, String.compare, etc, along with the Int.equal, String.equal.
[@@deriving show]produces a pretty-printing function for your type- Super helpful for debugging
type nucleotide = A | C | G | T [@@deriving eq, show];; (* example of multiple ppxs together *) type n_list = nucleotide list [@@deriving eq, show];; show_n_list [A;C;C];; (* returns the string "[A; C; C]" *) -
Enable use in top loop with directive
#require "ppx_deriving.show"and add to a library as(preprocess (pps (ppx_deriving.show)) - An easy short-cut is to use
std:#require "ppx_deriving.std"and(preprocess (pps ppx_deriving.std)), which gives youshow,eq,ord, and some others all in one go.
JSON format
- JSON is a common standard for data-as-text and may be useful in your project
- If you need JSON conversion, use
ppx_deriving_yojsonwhich works with theyojsonlibrary (notice its_yojsonnot.yojson- its someone elses library)
#require "ppx_deriving_yojson";;
# type nucleotide = A | C | G | T [@@deriving yojson];;
type nucleotide = A | C | G | T
val nucleotide_to_yojson : nucleotide -> Yojson.Safe.t = <fun>
val nucleotide_of_yojson :
Yojson.Safe.t -> nucleotide Ppx_deriving_yojson_runtime.error_or = <fun>
# type n_list = nucleotide list [@@deriving yojson];;
type n_list = nucleotide list
val n_list_to_yojson : n_list -> Yojson.Safe.t = <fun>
val n_list_of_yojson :
# let j = n_list_to_yojson [A;G;G];;
j : Yojson.Safe.t =
`List [`List [`String "A"]; `List [`String "G"]; `List [`String "G"]]
# let s = Yojson.Safe.to_string j;; (* function to get you the actual JSON *)
val s : string = "[[\"A\"],[\"G\"],[\"G\"]]" (* this is a json rep'n of a list *)
@@deriving in modules
@@deriving names things slightly differently when used in a module.
Suppose we made a module out of the nucleotide example, either by putting in a file nucleotide.ml or adding module Nucleotide = struct .. end to make a top-loop or nested module:
module Nucleotide = struct
type t = A | C | G | T [@@deriving eq]
let hamming_distance l = failwith "dummy"
end
- When this type was called
nucleotidenot in a module theppxmade a functionequal_nucleotide - Here the
ppxis smarter, instead ofNucleotide.equal_tit just makesNucleotide.equal-tis a special type in the module - Note that
[@@deriving ..]declarations in types in the.mlfile need to be repeated in the.mlifile if the types are not hidden