Git Product home page Git Product logo

ppx_easy_deriving's Introduction

ppx_easy_deriving

ci workflow status GitHub release status opam package status

Easily define PPX derivers without boilerplate and runtime overhead.

Installation

opam install ppx_easy_deriving

Examples

Equal

The following shows multiple ways of defining a PPX deriver for a standard equal function with various trade-offs.

Simple

The simplest way is to define equal (here called easy_equal2) for binary product and sum types, i.e. pairs and eithers. Using an isomorphism, all algebraic datatypes can be handled using the simple binary constructs and their base cases (unit and empty type). This is very similar to the simple derivers of ppx_type_directed_value, but with lower runtime overhead.

module EasyEqual2Arg: Simple.Variant.S =
struct
  (** Name of deriver. *)
  let name = "easy_equal2"

  (** Type of derived [easy_equal2] function for type [t]. *)
  let typ ~loc t = [%type: [%t t] -> [%t t] -> bool]

  (** [easy_equal2] function for [unit] type. *)
  let unit ~loc = [%expr fun () () -> true]

  (** [easy_equal2] function for a pair of types with given [easy_equal2] functions [e1] and [e2]. *)
  let both ~loc e1 e2 = [%expr fun (l1, l2) (r1, r2) -> [%e e1] l1 r1 && [%e e2] l2 r2]

  (** [easy_equal2] function for the empty type. *)
  let empty ~loc = [%expr fun _ _ -> true]

  (** [easy_equal2] function for an either of types with given [easy_equal2] functions [e1] and [e2]. *)
  let either ~loc e1 e2 =
    [%expr fun l r ->
      match l, r with
      | Either.Left l, Either.Left r -> [%e e1] l r
      | Either.Right l, Either.Right r -> [%e e2] l r
      | _, _ -> false
    ]

  (** Apply isomorphism to given [easy_equal2] function.
      This allows {!both} and {!either} to be applied to tuples, records and (polymorphic) variants of arbitrary size. *)
  let apply_iso ~loc easy_equal2 ~f ~f':_ =
    [%expr fun l r -> [%e easy_equal2] ([%e f] l) ([%e f] r)]
end

(* Create full deriver from simple deriver and register it with ppxlib. *)
module EasyEqual2Deriver = Deriver.Make (Simple.Variant.Reduce (EasyEqual2Arg))
let _ = EasyEqual2Deriver.register ()

Product

The isomorphisms of simple deriver definitions still incur some runtime overhead. A more efficient way to define equal (here called easy_equal) is to define it for entire products and sums in one go, instead of decomposing them to binary reductions. These definitions are more involved, especially variant since it also handles inline records in constructor arguments. However, the deriver defined this way is as efficient as a dedicated equal deriver, e.g. from ppx_deriving.eq or ppx_compare.

module EasyEqualArg: Product.Variant.S =
struct
  (** Name of deriver. *)
  let name = "easy_equal"

  (** Type of derived [easy_equal] function for type [t]. *)
  let typ ~loc t = [%type: [%t t] -> [%t t] -> bool]

  (** [easy_equal] function body for a product type with list of given [easy_equal] functions for the elements. *)
  let product_body ~loc es pel per =
    let body =
      let esl = Pat_exp.to_exps ~loc pel in
      let esr = Pat_exp.to_exps ~loc per in
      Util.map3 (fun e l r ->
          (* Apply [easy_equal] function per element. *)
          [%expr [%e e] [%e l] [%e r]]
        ) es esl esr
    in
    (* Reduce per-element [easy_equal] results to a result for the entire product. *)
    Util.reduce ~unit:[%expr true] ~both:(fun acc x ->
        [%expr [%e acc] && [%e x]]
      ) body

  (** [easy_equal] function for a product type with list of given [easy_equal] functions for the elements.
      [pe_create] allows creating patterns and expressions of the actual product type. *)
  let product ~loc ~pe_create es =
    let pel = pe_create ~prefix:"l" in
    let per = pe_create ~prefix:"r" in
    let body = product_body ~loc es pel per in
    let pl = Pat_exp.to_pat ~loc pel in
    let pr = Pat_exp.to_pat ~loc per in
    [%expr fun [%p pl] [%p pr] -> [%e body]]

  (** [easy_equal] function for a (polymorphic) variant type with list of given [easy_equal] functions for the (polymorphic) variant constructors. *)
  let variant ~loc ces =
    let cases = List.map (fun (c, c2, _es, es2) ->
        let pel = c ~prefix:"l" in
        let per = c ~prefix:"r" in
        let pel2 = c2 ~prefix:"l" in
        let per2 = c2 ~prefix:"r" in
        let body = product_body ~loc es2 pel2 per2 in
        let pa = Pat_exp.to_pat ~loc pel in
        let pb = Pat_exp.to_pat ~loc per in
        (* Equality case for one constructor with given product argument. *)
        case ~lhs:[%pat? [%p pa], [%p pb]]
          ~guard:None
          ~rhs:body
      ) ces
    in
    let fallback =
      (* Inequality case as fallback. *)
      case ~lhs:[%pat? _, _]
        ~guard:None
        ~rhs:[%expr false]
    in
    let body = pexp_match ~loc [%expr l, r] (cases @ [fallback]) in
    [%expr fun l r -> [%e body]]
end

(* Create full deriver from product deriver and register it with ppxlib. *)
module EasyEqualDeriver = Deriver.Make (Product.Variant.Make (EasyEqualArg))
let _ = EasyEqualDeriver.register ()

Performance

These two easy derivers are benchmarked against ppx_deriving.eq, ppx_compare, ppx_type_directed_value and refl. The benchmark involves an equality check on a 60-field record where only the last fields differ.

The table shows the product version (ppx_easy_deriving) being on par with ppx_deriving and ppx_compare. The simple version (ppx_easy_deriving2) is notably slower but still faster than ppx_type_directed_value (ppx_type_directed_equal) and refl.

Rate refl ppx_type_directed_equal ppx_easy_deriving2 ppx_deriving ppx_compare ppx_easy_deriving
refl 605433/s -- -73% -91% -97% -97% -97%
ppx_type_directed_equal 2245269/s 271% -- -65% -90% -90% -90%
ppx_easy_deriving2 6412029/s 959% 186% -- -71% -72% -72%
ppx_deriving 22347280/s 3591% 895% 249% -- -1% -1%
ppx_compare 22595512/s 3632% 906% 252% 1% -- -0%
ppx_easy_deriving 22639836/s 3639% 908% 253% 1% 0% --

See the full benchmark in example/ppx_easy_equal_bench/.

Lattice

example/ppx_lattice/ contains examples of additional product deriver helper functors by defining a deriver for product lattices with the following signature:

module type Lattice =
sig
  type t

  (* reduce2-like function *)
  val leq: t -> t -> bool (* reduce by conjunction *)

  (* map2-like function *)
  val join: t -> t -> t

  (* create-like function *)
  val bot: unit -> t

  (* reduce-like function *)
  val is_bot: t -> bool (* reduce by conjunction *)

  (* map-like function *)
  val relift: t -> t (* not really a lattice operation *)
end

Other libraries

  • ppx_type_directed_value โ€” has runtime overhead on every call of derived function.
  • refl โ€” requires runtime representations of types.

ppx_easy_deriving's People

Contributors

sim642 avatar

Stargazers

 avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.