I am currently trying to build a set over some type that can be parametrized with another type, eg :
module Test = struct
type 'a t = 'a another_extern_type
let compare t1 t2 = Stdlib.Int.compare t1.id t2.id
end
module St = Set.Make(Test)
In this particular case, the compare function is not dependant on 'a type. The problem is that Set.Make cannot be applied to partial modules. Is there any way to make this kind of thing (ie : having a ('a Test.t )Set.t produced by Set.Make) ?
You could parameterize the application of Set.Make by a module that tells which 'a to choose.
For example (not tested):
module F(X: sig type t end) = struct
module M = struct
type t = X.t Test.t
let compare = Test.compare
end
include Set.Make(M)
end
module N1 = F(Int) (* sets whose elements have type int Test.t *)
module N2 = F(String) (* sets whose elements have type string Test.t *)
I think it doesn’t apply well in my case : I am working on a project where I don’t have control over 'a type (to be more precise, the module I am developing will be called on other modules that can have a different 'a )
As a workaround (and of course depending on the functionality you need from Set) you could use Map.Make with the id being the keys and the actual 'a another_extern_type the value.
In particular you have the Map.merge function that you can use to implement union, intersection and difference (provided your .id uniquely identify the whole value, as you seem to show by their use in compare).
If the type 'a doesn’t matter and the entire comparison is on some integer ID in those records, then you can use an existential GADT wrapper to put such elements into a set:
type 'a foo = {
id: int;
value: 'a;
}
module E =
struct
type t = Foo: 'a foo -> t [@unboxed]
let compare (Foo {id; _}) (Foo {id = id'; _}) = Int.compare id id'
end
module S = Set.Make (E)
The GADT wrapping looks good. It might be slightly annoying to use as any interaction with set elements will need to wrap or unwrap.
Does the [@unboxed] annotation guarantees that both E.t and foo have the same runtime representation and that the wrapping and unwrapping operations are no-ops after compilation?
A point that is maybe not obvious in @sim642 implementation is that it is essentially equivalent to
storing only the ids in the Set
module Set = struct
include Set.Make(Int)
let add x s = add x.id s
let mem x s = mem x.id s
end
In particular, it is not possible to extract back a value from the set. If this is needed, another possible implementation would be to implement the set on top of a Map of ids:
let key x = x.Test.id
module Set: sig
type 'a t
val empty: 'a t
val mem: 'a Test.t -> 'a t -> bool
val add: 'a Test.t -> 'a t -> 'a t
val choose: 'a t -> 'a Test.t
end = struct
module Id_map = Map.Make(Int)
type 'a t = 'a Test.t Id_map.t
let empty = Id_map.empty
let add x s = Id_map.add (key x) x s
let mem x s = Id_map.mem (key x) s
let choose s = snd (Id_map.choose s)
end