我有一个可变集的签名:
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
(即集合中元素的类型)相同。如何限制类型相同?
答案 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_compare
或Base
模块使用它们。或者,如果您不使用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.Make
,Set.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
函数仅适用于哈希函数字典集。如果要使用多态可变集,则必须使用多态等式。