如何将Hashable模块的密钥类型限制为与签名中的type参数相同?

时间:2019-06-21 02:16:50

标签: ocaml functor ocaml-core

我有一个可变集的签名:

open Base
open Hashable

module type MutableSet = sig
  type 'a t
  val contains : 'a t -> 'a -> bool
end

我想使用基础库中的Hashable模块通过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

我认为问题在于,哈希键的类型不限于与'a(即集合中元素的类型)相同。如何限制类型相同?

3 个答案:

答案 0 :(得分:2)

问题的症结在于H.equal函数,其类型为'a t -> 'a t -> bool,例如,函数H.hash的类型为'a -> int。 我认为一切都出了问题,因为您对Base中的hashable意味着错误的假设。类型Hashable.t是三个函数的记录,其定义如下 1

type 'a t = { 
  hash : 'a -> int;
  compare : 'a -> 'a -> int;
  sexp_of_t : 'a -> Sexp.t;
}

因此,任何想要可哈希的类型都必须提供这三个功能的实现。而且,尽管存在可哈希模块的模块类型,但它并非旨在用作函子的参数。只有一个可哈希模块,该模块定义可哈希值的接口(如果需要,可输入类型类)。

因此,如果需要可散列的键的单态MutableSet,则应编写一个仿函数,该仿函数使用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;;

如果要实现多态MutableSet,则无需编写函子(如果是polymoprhic,则已经为所有可能的类型定义了函子。。)。您甚至可以使用Hashable模块本身的多态函数,例如

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_compareBase模块使用它们。或者,如果您不使用Base或Core库,则默认情况下compare中的Stdlib函数是多态的,类型为'a -> 'a -> int

话虽如此,我认为我们需要对多态版本的内容进行澄清。 Base的Hash_set以及Hashtbl也是多态数据结构,因为它们分别具有类型'a t('k,'a) t,它们的键名都是多态的。但是,它们不依赖于多态比较功能,而是需要用户在构造过程中提供比较功能。实际上,它们需要hashable接口的实现。因此,要创建一个空的哈希表,您需要向其传递一个实现该哈希表的模块,例如

let empty = Hashtbl.create (module Int)

所传递的模块必须在其中实现Hashable.Key接口,该接口通过hashable函数提供了Hashable.of_key的实现。而散列表的实现只是将比较函数本身(例如,大概)存储在其中

type ('a,'k) hashtbl = {
  mutable buckets : Avltree.t array;
  mutable length : int;
  hashable : 'k hashable;
}

我认为,考虑到这种实现方式,现在我们需要与可哈希记录进行比较时更加明显。

  

一个版本(Functor与多态同构)比另一版本更可取吗?

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

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

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

type ('a,'comparator_witness) set

是与比较函数耦合的二叉树,通过'comparator_witness类型反映在集合类型中,因此对于每个比较函数都会生成一个新的新类型,从而防止了Set.union等人在两个具有不同比较功能的集合之间存储的操作。

同时还有一个Set.Make(K : Key)仿函数,它创建一个提供type t = (K.t,K.comparator_witness) set类型的模块,从理论上讲,它可以从内联中受益。此外,实现Core.Comparable.S及以下版本的每个模块还将提供.Map.Set等模块,例如Int.Set。这些模块通常是通过相应的Make函数(即Map.MakeSet.Make)创建的,但是它们为手工专业化提供了机会。


1)因此,Hashable.equal函数实际上是比较函数而不是值。它基本上比较两个类型类。而且,我相信Hashable.hash函数的键入是偶然的'a -> int,而预期的类型也是'a t -> int

答案 1 :(得分:0)

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

我猜这是。不过目前无法检查。

答案 2 :(得分:0)

问题在于,{p>中的等式H.equal

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

是哈希函数字典('a H.t)上的等式。因此,如所写,contains函数仅适用于哈希函数字典集。如果要使用多态可变集,则必须使用多态等式。