What should I use for dict with opaque type

Hi !

In my applications, I’m using a string opaque type for IDs. Sometimes, I’m using a Js.Dict.t with an ID as key and it works well but !
I would like to enforce the type of the key as an opaque type for a better readability and avoid human mistakes.

module OrderId = MakeStringIdentifier();

type someDict = Js.Dict.t<string>  // how enforce the OrderId.t as key ?

Should I go to something like Belt.Map.Dict or there is way to stay with the Js.Dict.t by creating an abstraction with a functor ?

Thanks for your help :slight_smile:

1 Like

Not sure Js.Dict supports something else for the key (out of the box), but you could use a Map, either from Belt or RescriptCore, e.g.

let orders: Map.t<OrderId.t, string> = Map.make()

orders->Map.set(OrderId.make("abc"), "Carrots")

or simply

let orders = Map.make()

orders->Map.set(OrderId.make("abc"), "Carrots")
orders->Map.get("abc") // Errors

If you’re using Belt, you could do something like this (using MutableMap for parity with Rescript Core’s Map)

module OrderIdComparable = Belt.Id.MakeComparableU({
  type t = OrderId.t
  let cmp = (a, b) => Pervasives.compare(OrderId.view(a), OrderId.view(b))
})

let orders = Belt.MutableMap.make(~id=module(OrderIdComparable))

orders->Belt.MutableMap.set(OrderId.make("abc"), "Carrots")

You can indeed easily use a functor for this:

module type Id = () =>
{
  type t
  let make: string => t
  module Dict: {
    type key = t
    type t<'a>
    let get: (t<'a>, key) => option<'a>
    @set_index
    external set: (t<'a>, key, 'a) => unit = ""
    @val
    external keys: t<'a> => array<string> = "Object.keys"
    @obj /** Returns an empty dictionary. */
    external empty: unit => t<'a> = ""
    let unsafeDeleteKey: (. t<string>, string) => unit
    let entries: t<'a> => array<(key, 'a)>
    let values: t<'a> => array<'a>
    let fromList: list<(key, 'a)> => t<'a>
    let fromArray: array<(key, 'a)> => t<'a>
    let map: ((. 'a) => 'b, t<'a>) => t<'b>
  }
}

module Id: Id = () => {
  type t = string
  let make = s => s
  module Dict = Js.Dict
}

module MyId = Id()
let myId = MyId.make("foo")
let myIdDict = MyId.Dict.empty()
myIdDict->MyId.Dict.set(myId, "foovalue")

4 Likes

A Map seems great for most of the case !
I didn’t mention the need to store the Dict in the localStorage, so I tend to use Js.Dict.t to stay close to the object format.

1 Like

That’s exacty what I thought, I would need to rewrite some bindings !

I’ll go with that since it matches perfectly my use case :slight_smile:

Thanks for your helps !

1 Like

No need to rewrite any bindings. module Dict = Js.Dict and you’re done. Internally in the functor it knows that the key type is a string.

1 Like

Got it! You could always normalise it (e.g. toArray, fromArray), but I can see how that might not always be desirable. :slight_smile: