Ocaml 如何将哈希模块的密钥类型约束为与签名中的类型参数相同?

Ocaml 如何将哈希模块的密钥类型约束为与签名中的类型参数相同?,ocaml,functor,ocaml-core,Ocaml,Functor,Ocaml Core,我有一个可变集的签名: open Base open Hashable module type MutableSet = sig type 'a t val contains : 'a t -> 'a -> bool end 我想使用基本库中的模块实现HashSet签名 module HashSet(H : Hashable) : MutableSet = struct let num_buckets = 16 type 'a t = { mutable buck

我有一个可变集的签名:

open Base
open Hashable

module type MutableSet = sig
  type 'a t
  val contains : 'a t -> 'a -> bool
end
我想使用基本库中的模块实现HashSet签名

module HashSet(H : Hashable) : MutableSet = struct
  let num_buckets = 16
  type 'a t = { mutable buckets : ('a list) array }
  let contains s e =
    let bucket_index = (H.hash e) % num_buckets in
    let bucket = s.buckets.(bucket_index) in
    List.exists ~f:(fun e' -> H.equal e e') bucket
end
我发现了错误

Error: Signature mismatch:
       Modules do not match:
         sig
           type 'a t = { mutable buckets : 'a list array; }
           val contains : 'a H.t t -> 'a H.t -> bool
         end
       is not included in
         MutableSet
       Values do not match:
         val contains : 'a H.t t -> 'a H.t -> bool
       is not included in
         val contains : 'a t -> 'a -> Base.bool
我认为问题在于散列键的类型没有被限制为与集合中元素的类型相同。如何将类型约束为相同的

module HashSet(H : Hashable) : (MutableSet with type t = H.t)

我想是这个。但是,目前无法检查。

问题是中的等式
H.equal

List.exists ~f:(fun e' -> H.equal e e') bucket

是哈希函数字典(
'a H.t
)上的等式。因此,正如所写的那样,
contains
函数只对哈希函数字典集起作用。如果你想要一个多态可变集合,你就必须使用多态等式。

问题的关键是
H.equal
函数,它的类型是
'a t->'a t->bool
,它与
H.hash
函数的类型是
'a->int
。 我认为一切都错了,因为你对hashable在Base中的含义的错误假设。类型
Hashable.t
是三个函数的记录,定义如下1:

因此,任何想要散列的类型都必须提供这三个函数的实现。虽然有一个模块类型的模块Hashable,但它并不是设计用来作为函子的参数。只有一个模块Hashable,它定义了一个Hashable值的接口(如果需要,可以使用type class)

因此,如果您需要一个单态可变集来表示可散列的键,那么您应该编写一个函子,它接受类型为
hashable.key
的模块

module HashSet(H : Hashable.Key) = struct
  let num_buckets = 16
  type elt = H.t
  type t = { mutable buckets : H.t list array }

  let contains s e =
    let bucket_index = H.hash e % num_buckets in
    let bucket = s.buckets.(bucket_index) in
    List.exists ~f:(fun e' -> H.compare e e' = 0) bucket
end;;
如果要实现多态可变集,则不需要编写函子(如果它是polymoprhic,则已为所有可能的类型定义了函子)。您甚至可以使用哈希模块本身的多态函数,例如

module PolyHashSet = struct
  let num_buckets = 16
  let {hash; compare} = Hashable.poly

  type 'a t = { mutable buckets : 'a list array }

  let contains s e =
    let bucket_index = hash e % num_buckets in
    let bucket = s.buckets.(bucket_index) in
    List.exists ~f:(fun e' -> compare e e' = 0) bucket
end
对后续问题的答复 您希望何时使用Hashable.equal来比较两个类型类

1) 当您需要确保两个哈希表使用相同的比较函数时。例如,如果要合并两个表或使两个表相交,则它们应使用相同的比较/哈希函数,否则,结果未定义

2) 当您需要比较两个哈希表是否相等时

有没有一种方法可以定义多态版本而不使用内置的多态哈希函数和equals方法

如果“内置”是指OCaml提供的原语,那么答案是:不,这样的哈希表必须使用OCaml标准库中的多态比较原语

您不必使用基本库中的
Hashable
模块来访问它们。它们也可以通过
Caml
Polymorphic\u compare
模块在
Base
中获得。或者,如果您没有使用基本库或核心库,那么默认情况下,来自Stdlib的
compare
函数是多态的,并且具有类型
'a->'a->int

尽管如此,我认为需要对我们所说的多态版本进行一些澄清。基的Hash_集以及Hashtbl也是多态数据结构,因为它们具有相应的类型
'at
('k,'a)t
,这两种类型的键都是多态的。然而,它们不依赖多态比较函数,而是要求用户在构建过程中提供比较函数。事实上,它们需要实现
hashable
接口。因此,要创建空哈希表,需要向其传递一个实现该哈希表的模块,例如

let empty = Hashtbl.create (module Int)
其中,通过的模块必须实现
Hashable.Key
接口,该接口通过
Hashable.of_Key
函数提供
Hashable
的实现。哈希表实现只是将比较函数存储在自身中,例如

type ('a,'k) hashtbl = {
  mutable buckets : Avltree.t array;
  mutable length : int;
  hashable : 'k hashable;
}
我认为,考虑到这种实现,当我们需要与可散列记录进行比较时,它现在变得更加明显

一个版本(带函子的单态vs多态)比另一个更可取吗

首先,我们实际上有三个版本。Functor,多态,以及使用多态比较函数的函数(让我们将其命名为universal)。后者是最不可取的,应尽可能避免

关于前两个,两者都是好的,但是多态版本在不涉及太多妥协的情况下更通用。从理论上讲,函子版本为编译器优化提供了更多的机会(因为比较函数可以内联),但它的代价是,每个键都有不同的模块/类型

您还可以从这两种方法中获益,并提供多态和单态实现(后者是前者的特化),例如,这就是在JS Base/Core中实现映射和集的方式。集合有一个多态类型

type ('a,'comparator_witness) set
这是一个二叉树,与比较函数耦合,通过
的比较器见证
类型反映在集合类型中,从而为每个比较函数生成一个新的类型,从而防止存储有不同比较函数的两个集合之间的
set.union
等操作

同时还有一个
Set.Make(K:Key)
functor,它创建了一个模块,提供
type t=(K.t,K.comparator\u-witness)Set
type,理论上可以从内联中获益。此外,实现
Core.Comparable.S
及以下功能的每个模块还将提供
.Map
.Set
等模块,例如
Int.Set
。这些模块通常通过相应的
Make
函子(即type ('a,'comparator_witness) set