# Issues with designing something with module types

**URL:** <https://discuss.ocaml.org/t/issues-with-designing-something-with-module-types/5889>\
**Category:** Learning\
**Created:** [June 1, 2020, 7:02am UTC](https://discuss.ocaml.org/t/issues-with-designing-something-with-module-types/5889 "2020-06-01T07:02:50Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![tcoopman](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/tcoopman/32/843_2.png) [@tcoopman](https://discuss.ocaml.org/u/tcoopman)\
**Post date:** [June 1, 2020, 7:02am UTC](https://discuss.ocaml.org/t/issues-with-designing-something-with-module-types/5889/1 "2020-06-01T07:02:50Z")

</div>

I have some module type `Foo` like below. I have multiple implementations of this module type and want to group them in a `Map` or `Hashtbl` based on a key. The key is a string and same string means same module.

This is the code

```ocaml
module type Foo = sig
  type t
  val make : t
  val do_it : t -> t
end

module A : Foo = struct
  type t = int
  let make = 1
  let do_it t = t + 1
end

module B : Foo = struct
  type t = string
  let make = "a"
  let do_it t = t ^ t
end

type 'a x = (module Foo with type t = 'a)
type bound = B: 'a x * 'a -> bound

```

The constraint that I want to put into a type is that for a key I’ll always save the same module implementation. So something like this (this is obviously not working pseudocode):

```ocaml
type map = ???
let map = ????

let update map key data = 
  Map.update map key ~f:(function
    | None -> [data]
    | Some x -> data :: x
  )

let do_something map key =
  Map.find map key
  |> Option.map ~f:(fun ((module X), x) -> X.do_it x)
  |> ignore

```

I’ve been trying to work something out with `GADT`s but I don’t know how to link the key with the specific modules.

Any idea how to solve this? The `Map` or `Hashtbl` are not real requirements if it’s possible to solve it in an other way. So the only real requirement is that I need a function like `update` and `do_something`

Thanks

---

<div class="post-metadata">

**Author:** ![octachron](https://avatars.discourse-cdn.com/v4/letter/o/49beb7/32.png) [@octachron](https://discuss.ocaml.org/u/octachron)\
**Post date:** [June 1, 2020, 12:53pm UTC](https://discuss.ocaml.org/t/issues-with-designing-something-with-module-types/5889/2 "2020-06-01T12:53:52Z")

</div>

If you want to enforce that every element in the map share the same type, that can be done with a slight modification of the `bound` type:

```ocaml
type bound = B: 'a x * 'a list -> bound

```

If you end up with a lot of similar type, you may want to define a functor for defining those.

If you wanted to have map, with a type ` key:string -> map -> f(key)`, this is impossible because the type of the result cannot depend on the value of the key parameter.  
However, if you are fine with a type: ` 'a key -> map -> 'a`, where the key is bound to a given type, this is possible and is generally called an hmap. There are various implementation availalble on opam. For instalce, [hmap](https://erratique.ch/software/hmap).  
The key trick for defining those hmap is to use extensible variant types as a collection of keys:

```auto
type _ keys = ..
module type key = sig
  type t
  type _ keys += Key: t keys
end

```

We can use a first-class module to pack everything at the value level

```auto
type 'a key = (module key with type t = 'a)
let new_key (type a) (): a key = 
  (module struct type t = a type _ keys += Key: a keys end)

```

Then, we can use pattern matching to recognize a specific key and its associated type:

```ocaml
type bound = B: 'a keys * 'a -> bound
let extract (type a) ((module K): a key) (B(k,x)) =
match k with
  | K.Key -> Some (x:a)
  | _ -> None;

```

Note that you might need to customize the keys of data, but that is not that hard, once the trick known.

---

<div class="post-metadata">

**Author:** ![tcoopman](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/tcoopman/32/843_2.png) [@tcoopman](https://discuss.ocaml.org/u/tcoopman)\
**Post date:** [June 1, 2020, 2:43pm UTC](https://discuss.ocaml.org/t/issues-with-designing-something-with-module-types/5889/3 "2020-06-01T14:43:28Z")

</div>

Thanks, that seems to bring me a lot closer to the solution, but it seems I’m still missing something:

```ocaml
module type Foo = sig
  type t

  val make : t

  val do_it : t -> t
end

module A : Foo = struct
  type t = int

  let make = 1

  let do_it t = t + 1
end

module B : Foo = struct
  type t = string

  let make = "a"

  let do_it t = t ^ "a"
end

type 'a x = (module Foo with type t = 'a)

type bound = B : ('a x * 'a) list -> bound

let key_a = Hmap.Key.create ()

let key_b = Hmap.Key.create ()

let map = Hmap.empty

let map = Hmap.add key_a (B [((module A), A.make)]) map

let map = Hmap.add key_b (B [((module B), B.make)]) map

let () =
  let _map = match Hmap.find key_a map with
  | Some (B modules) ->
      let x = B (((module A), A.make):: modules) in
      Hmap.add key_a x map
  | None ->
      failwith "TODO"
  in
  ()

```

This fails with:

```auto
43 | let x = B (((module A), A.make):: modules) in
                                             ^^^^^^^
Error: This expression has type ($B_'a x * $B_'a) list
       but an expression was expected of type (A.t x * A.t) list
       Type $B_'a x = (module Foo with type t = $B_'a)
       is not compatible with type A.t x = (module Foo with type t = A.t) 
       Type $B_'a is not compatible with type A.t 

```

It’s obvious that this can’t work because I could put anything in that list. But does this mean that I can’t update this list dynamically at runtime or am I just missing something?

---

<div class="post-metadata">

**Author:** ![octachron](https://avatars.discourse-cdn.com/v4/letter/o/49beb7/32.png) [@octachron](https://discuss.ocaml.org/u/octachron)\
**Post date:** [June 1, 2020, 3:01pm UTC](https://discuss.ocaml.org/t/issues-with-designing-something-with-module-types/5889/4 "2020-06-01T15:01:31Z")

</div>

I was more thinking of:

```ocaml

type 'a elt = Elt of 'a x * 'a
let key_a = Hmap.Key.create ()
let key_b = Hmap.Key.create ()
let map = Hmap.add key_a ([Elt ((module A), A.make)]) map
let map = Hmap.add key_b ([Elt((module B), B.make)]) map
let _ =
  match Hmap.find key_a map with
  | Some ( Elt ((module A),_) :: _ as modules) ->
    let x = (Elt((module A), A.make)):: modules in
    Hmap.add key_a x map
  | None | Some [] ->
    failwith "TODO"

```

since from your description I though that you wanted to have only one type of implementation by key.  
In other words, the heterogeneous map is already hiding the type of the implementation from the outside, there is no need to hide it a second time with `bound`.  
Note that the definition of `elt` is only here to avoid few module type annotation.

---

<div class="post-metadata">

**Author:** ![tcoopman](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/tcoopman/32/843_2.png) [@tcoopman](https://discuss.ocaml.org/u/tcoopman)\
**Post date:** [June 1, 2020, 5:39pm UTC](https://discuss.ocaml.org/t/issues-with-designing-something-with-module-types/5889/5 "2020-06-01T17:39:29Z")

</div>

It seems I’m not explaining my problem well with my examples, sorry for that. I’ll try to start over with the actual problem instead of examples.

I’m trying to implement a library similar to Phoenix `Channels` (a websocket implementation for elixir [https://hexdocs.pm/phoenix/channels.html](https://hexdocs.pm/phoenix/channels.html)). So when someone wants to use the library they have to implement 2 things:

1. modules that implement some module type, concretely something like this:

```ocaml
module type Channel = sig
  type t
  type id
  type reply = `Reply of string | `ToChannel of id * string
  val make : t
  val handle_in : t -> string -> reply * t
  val filter: t -> id -> bool
end

```

and 2. a function `get_channel: topic:string -> (module Channel)`

So on the server you have multiple `Channel` implementations. For example a `ChatChannel` and `GameChannel`. When a browser connects to a websocket, they can join a channel. Joining a channel happens based on a topic (string), so you could do something like this in the browser: `ws.join("chat")` and then on the server a new `Channel` would be created with `Channel.make` and that would keep the state for that connection.

Incoming messages to the `Channel` are sent to `handle_in`. This returns a `reply * t`. If the reply is of type `Reply` it’s send directly to the browser, but if the reply is of type `ToChannel`. Each channel that is connected with the same `topic:string` should receive the message and that `Channel` can decide to `filter` it based on the `id`.

So it’s this last part that is giving me trouble. How to implement the `filter` functionality?  
My library doesn’t know anything about the `Channel` implementations and I think we can constraint the requirement about `string -> (module Channel)` (because that mapping is known at compile time) to `key -> (module Channel)`, but I’m still lost on how to implement this.

I hope I explained it a bit better this time and didn’t add to the confusion. Thanks a lot for the answers so far, I already learned a lot.

---

<div class="post-metadata">

**Author:** ![octachron](https://avatars.discourse-cdn.com/v4/letter/o/49beb7/32.png) [@octachron](https://discuss.ocaml.org/u/octachron)\
**Post date:** [June 1, 2020, 6:22pm UTC](https://discuss.ocaml.org/t/issues-with-designing-something-with-module-types/5889/6 "2020-06-01T18:22:37Z")

</div>

Does decoupling the implementation/protocol from the various channel instances work?

```ocaml
type ('a,'b) channel = (module Channel with type t = 'a and type id = 'b)
type ('a,'b) topic = {
  protocol: ('a,'b) channel;
  instances: 'a list
}
let filter (type a b)
  { protocol = ((module P): (a,b) channel) ; instances } id =
  List.filter (fun i -> P.filter i id) instances

```

And you can store them in an ordinary map with just

```ocaml
type dyn_topic = Dyn: ('a,'b) topic -> ('a,'b) topic

```

Note that since `Channel` does not contain parametric type constructors, it might be lighter to use a record of function:

```ocaml
type ('a,'b) reply = [`Reply of string | `ToChannel of id * string] 
type ('t,'id) channel = {
  make : 't;
  handle_in : 't -> string -> ('t,'id) reply * 't;
  filter: 't -> id -> bool
}

```

---

<div class="post-metadata">

**Author:** ![tcoopman](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/tcoopman/32/843_2.png) [@tcoopman](https://discuss.ocaml.org/u/tcoopman)\
**Post date:** [June 2, 2020, 7:01am UTC](https://discuss.ocaml.org/t/issues-with-designing-something-with-module-types/5889/7 "2020-06-02T07:01:15Z")

</div>

I’m not sure that this fixes the issue completely.

So when a web browser sends a message, it goes to a specific channel. That specific channel sends a reply. When that reply is `ToChannel` I want to send the `id * string` to the filter function.  
The problem is that I can’t seem to find a way to say the type of module that returned this `reply` is the same type as the one for this `topic`, because they don’t come from the same place.  
But I think I have an idea on how to fix that.

I’m going to try to find some time today or tomorrow to test that out and come back here to let you know if it worked.
