Understanding `Map`/`Set` `identity` (in modules)

I have the following contrived example:

https://rescript-lang.org/try?code=PYBwpgdgBAQmA2AXAUMgtsAJgV3mKAkhIgMJohQC8sCiAdAZnQLICGA1mCcOawE6sARngAUAb2RQoiAJ7hpVKAEtikqHkRQAxuUUjWAGiiCAlFQB8UAApg+AN1YBnJXbCO6WniH5h9R08gAviboWLj4svIAouSyAMpgmtQSUpH4DvDYYGppypiQiEqyahpQYLEyAFxQCfSIADwZWUZK+cRFMuZBoTh4UGycMSDxidVDI0lQKdJy6ayZ+NQqKKmzeQUdikSk5HStG8VSpeXDMoq1DMR0J8WByEA

It looks like I am not defining type identity correctly in MakeEmptySet. Is what I am trying to do here possible?

For some context, I would like to turn MakeEmptySet into a functor that creates an empty set for a given type and comparator.

1 Like

Here’s how you would accomplish this:

open Belt
module IntCmp = Belt.Id.MakeComparable({
  type t = int
  let cmp = (a, b) => Pervasives.compare(a, b)
})
module type EmptySet = {
  type value
  type identity
  let empty: Set.t<value, identity>
}
module MakeEmptySet: EmptySet = {
  type value = int
  type identity = IntCmp.identity
-  let empty = Set.Int.empty
+  let empty = Set.make(~id=module(IntCmp))
}

Even though your custom IntCmp module does the same thing that Belt.Set.Int does internally, it has a different identity type, so the two modules aren’t interchangeable. You’ll always need to use your custom IntCmp module with the generic Belt.Set.

What you’re doing is possible, but I’m not sure if it’s necessary. Turning MakeEmptySet into functor seems redundant when the Belt.Id.MakeComparable functor should already do the work you need. You should just have to use Set.make(~id=module(MyCustomIntCmp)) to create a unique empty set.

A simpler pattern for making new types of sets is this:

open Belt
module IntCmp = Id.MakeComparable({
  type t = int
  let cmp = (a, b) => compare(a, b)
})
module AnotherIntCmp = Id.MakeComparable({
  type t = int
  let cmp = (a, b) => compare(b, a)
})
let emptySet = Set.make(~id=module(IntCmp))
let anotherEmptySet = Set.make(~id=module(AnotherIntCmp))

Part of the motivation behind using Belt.Id.MakeComparable is to avoid using “big” functors, like the ones included in the OCaml stdlib (e.g. Set.Make), so you can just use the generic Belt.Set functions instead.

Hongbo wrote a more in-depth blog post about this technique a while ago.

4 Likes

Hi JohnJ. This was exactly what I needed. The example a I gave in the playground is a much simplified of my actual application, which was essentially how to instantiate something looking like this:

module type AModuleType = {
  type value
  type identity
  let aFunction: value => Map.t<value, int, identity>
}

Thanks for the reference to the blog post also.

2 Likes