Motivation for ppx_hash

Hi there,

I am reading a code snippet, say module T, which is using ppx_hash

open Core

type t = int [@@deriving sexp, compare, hash]

include Comparable.Make (Int)

This module is serving as a temporary variable in the project. I omitted some helper functions which are basically counting the number of temporary created. The idea is we can build a Hashtbl from string(variable name) to this temporary.

However, I think using ppx_hash and functor in this module is too much. Why not just use int? Is there any motivation or scenarios for applying a ppx_hash?

PS: I noticed this demo is also showed in the official document, but it only shows the usage instead of the motivation and benefits it provides.

1 Like

I think using ppx_hash is a way to quickly define the val hash : t -> int function that is necessary to be able to pass T as argument to the Hashtbl.Make functor. Also, the hash function defined by ppx_hash for int may be specialized to integers, as opposed to the polymorphic hash function that you would typically use if you defined hash by hand.


1 Like

If you’re using Base or Core, one thing you may want to use ppx_hash for is to satisfy the signature required to use your custom types as keys in hash tables.

Here is an example.

open! Base

module Book = struct
  type t = { title : string; isbn : string } [@@deriving compare, hash, sexp_of]

let books = Hashtbl.create (module Book2)

If you check out the signature for Base.Hashtbl.Key.S, you see this:

type t
val compare : t -> t -> int
val sexp_of_t : t -> Sexplib0.Sexp.t
val hash : t -> int

And the ppxes give you those with the @@deriving.

Check out the section in Real World OCaml for more info.

As to why you might want to use ppx_hash if your type is just an int, you could imagine a case where you have some module like this:

open! Base 

module Foo = struct
  type t = int [@@deriving compare, hash, sexp_of]

  (* Lots more stuff... *)

let foo_ht = Hashtbl.create (module Foo)

Without the [@@deriving compare, hash, sexp_of] (or by defining the functions they create by hand), you wouldn’t be able to use Foo as a key.

By the way, if you want to also use the type as a key in a Map or member of a Set, you will also need comparator and comparator_witness. You can generate those automatically with a pattern like this:

open! Base

module Foo = struct
  module T = struct
    type t = int [@@deriving compare, hash, sexp_of]

  include T
  include Comparator.Make (T)

  (* Lot's of stuff. *)

let foo_ht = Hashtbl.create (module Foo)
let foo_map = Map.empty (module Foo)
let foo_set = Set.empty (module Foo)

If you only want to use Foo in maps and sets, you don’t actually need the @@deriving hash for that.

Note: I see you used Comparable.Make…that will work as well as it does give you comparator and comparator_witness too, but it is only needed instead of Comparator.Make if you need the extra functions it gives.

Finally, if you want to use your custom type as a value and not as the key, then yeah, you won’t need to bother with the deriving and functors. You just need them if you want to use it as the key of Hashtbl, Map, and Set.


Thank you so much! It clarifies a lot for me :smile: !

1 Like