In OCaml using Base, how do you construct a set with elements of type `int * int`?

陌路散爱 提交于 2020-01-24 17:09:12

问题


In F#, I'd simply do:

> let x = Set.empty;;
val x : Set<'a> when 'a : comparison

> Set.add (2,3) x;;
val it : Set<int * int> = set [(2, 3)]

I understand that in OCaml, when using Base, I have to supply a module with comparison functions, e.g., if my element type was string

let x = Set.empty (module String);;
val x : (string, String.comparator_witness) Set.t = <abstr>

Set.add x "foo";;
- : (string, String.comparator_witness) Set.t = <abstr>

But I don't know how to construct a module that has comparison functions for the type int * int. How do I construct/obtain such a module?


回答1:


There are examples in the documentation for Map showing exactly this.

If you use their PPXs you can just do:

module IntPair = struct
  module T = struct
    type t = int * int [@@deriving sexp_of, compare] 
  end

  include T
  include Comparable.Make(T)
end

otherwise the full implementation is:

module IntPair = struct
  module T = struct
    type t = int * int
    let compare x y = Tuple2.compare Int.compare Int.compare
    let sexp_of_t = Tuple2.sexp_of_t Int.sexp_of_t Int.sexp_of_t
  end

  include T
  include Comparable.Make(T)
end

Then you can create an empty set using this module:

let int_pair_set = Set.empty (module IntPair)



回答2:


To create an ordered data structure, like Map, Set, etc, you have to provide a comparator. In Base, a comparator is a first-class module (a module packed into a value) that provides a comparison function and a type index that witnesses this function. Wait, what? Later on that, let us first define a comparator. If you already have a module that has type

 module type Comparator_parameter = sig 
     type t (* the carrier type *)

     (* the comparison function *)
     val compare : t -> t -> int 

     (* for introspection and debugging, use `sexp_of_opaque` if not needed *)
     val sexp_of_t : t -> Sexp.t
 end

then you can just provide to the Base.Comparator.Make functor and build the comparator

 module Lexicographical_order = struct 
    include Pair
    include Base.Comparator.Make(Pair)
 end

where the Pair module provides the compare function,

 module Pair = struct
   type t = int * int [@@deriving compare, sexp_of]
 end

Now, we can use the comparator to create ordered structures, e.g.,

 let empty = Set.empty (module Lexicographical_order)

If you do not want to create a separate module for the order (for example because you can't come out with a good name for it), then you can use anonymous modules, like this

 let empty' = Set.empty (module struct
   include Pair
   include Base.Comparator.Make(Pair)
  end)

Note, that the Pair module, passed to the Base.Comparator.Make functor has to be bound on the global scope, otherwise, the typechecker will complain. This is all about this witness value. So what this witness is about and what it witnesses.

The semantics of any ordered data structure, like Map or Set, depends on the order function. It is an error to compare two sets which was built with different orders, e.g., if you have two sets built from the same numbers, but one with the ascending order and another with the descending order they will be treated as different sets.

Ideally, such errors should be prevented by the type checker. For that we need to encode the order, used to build the set, in the set's type. And this is what Base is doing, let's look into the empty' type,

val empty' : (int * int, Comparator.Make(Pair).comparator_witness) Set.t

and the empty type

val empty : (Lexicographical_order.t, Lexicographical_order.comparator_witness) Set.t

Surprisingly, the compiler is able to see through the name differences (because modules have structural typing) and understand that Lexicographical_order.comparator_witness and Comparator.Make(Pair).comparator_witness are witnessing the same order, so we can even compare empty and empty',

# Set.equal empty empty';;
- : bool = true

To solidify our knowledge lets build a set of pairs in the reversed order,

module Reversed_lexicographical_order = struct
  include Pair
  include Base.Comparator.Make(Pair_reveresed_compare)
end

let empty_reveresed =
  Set.empty (module Reversed_lexicographical_order)

(* the same, but with the anonyumous comparator *)
let empty_reveresed' = Set.empty (module struct
    include Pair
    include Base.Comparator.Make(Pair_reveresed_compare)
  end)

As before, we can compare different variants of reversed sets,

# Set.equal empty_reversed empty_reveresed';;
- : bool = true

But comparing sets with different orders is prohibited by the type checker,

# Set.equal empty empty_reveresed;;
Characters 16-31:
  Set.equal empty empty_reveresed;;
                  ^^^^^^^^^^^^^^^
Error: This expression has type
         (Reversed_lexicographical_order.t,
          Reversed_lexicographical_order.comparator_witness) Set.t
       but an expression was expected of type
         (Lexicographical_order.t, Lexicographical_order.comparator_witness) Set.t
       Type
         Reversed_lexicographical_order.comparator_witness =
           Comparator.Make(Pair_reveresed_compare).comparator_witness
       is not compatible with type
         Lexicographical_order.comparator_witness =
           Comparator.Make(Pair).comparator_witness 

This is what comparator witnesses are for, they prevent very nasty errors. And yes, it requires a little bit of more typing than in F# but is totally worthwhile as it provides more typing from the type checker that is now able to detect real problems.

A couple of final notes. The word "comparator" is an evolving concept in Janestreet libraries and previously it used to mean a different thing. The interfaces are also changing, like the example that @glennsl provides is a little bit outdated, and uses the Comparable.Make module instead of the new and more versatile Base.Comparator.Make.

Also, sometimes the compiler will not be able to see the equalities between comparators when types are abstracted, in that case, you will need to provide sharing constraints in your mli file. You can take the Bitvec_order library as an example. It showcases, how comparators could be used to define various orders of the same data structure and how sharing constraints could be used. The library documentation also explains various terminology and gives a history of the terminology.

And finally, if you're wondering how to enable the deriving preprocessors, then

  1. for dune, add (preprocess (pps ppx_jane)) stanza to your library/executable spec
  2. for ocamlbuild add -pkg ppx_jane option;
  3. for topelevel (e.g., ocaml or utop) use #require "ppx_jane";; (if require is not available, then do #use "topfind;;", and then repeat).


来源:https://stackoverflow.com/questions/59265721/in-ocaml-using-base-how-do-you-construct-a-set-with-elements-of-type-int-int

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!