Troubles using a simple functorised module for a list type

I’m playing with first-class modules in OCaml, and since I’m still a noob, I stumbled upon a compiler error I can’t resolve on my own. So I’d appreciate any help! :pray:

I want to make a simple module for a semigroup like this one:

module type Semigroup = sig
  type t
  val append : t -> t -> t
end

This works perfectly, and I can even create some trivial instances:

module Int_semigroup : Semigroup = struct
  type t = int
  let append = (+)
end

However, the following doesn’t work when I’m creating a semigroup module for a list:

module List_semigroup : Semigroup = struct
  type t = 'a t
  let append = (@)
end

The error message is the following (and it makes sense so far):

Error: The type constructor t expects 0 argument(s),
but is here applied to 1 argument(s)

I found the following way to define such modules for types that should be parametrised by variables (I don’t know if it’s idiomatic in OCaml though). The List_semigroup just needs to be a functor that takes a module with just a type inside:

module List_semigroup (T: sig type t end) : Semigroup = struct
  type t = T.t list
  let append = (@)
end

This compiles. However, now I’m getting problems using this module. The following code doesn’t compile:

module L = List_semigroup(struct type t = int end)

let example =
  let l1 = [3; 1; 2] in
  let l2 = [0, 1, 2] in 
  L.append l1 l2

and the error is (highlighting l1 inside `L.append l1 l2):

Error: This expression has type int list but an expression was expected of type L.t

Could someone explain how to resolve this error and make my example compile?

Note that you have the same problem with your Int_semigroup:

Int_semigroup.append 1 2;;
Error: This expression has type int but an expression was expected of type
         Int_semigroup.t

That’s because in the signature of Semigroup the type t is private, so external users of the module are not supposed to know what it is.

You can make it public with

module Int_semigroup : Semigroup with type t = int = struct
  type t = int
  let append = (+)
end;;

Then

Int_semigroup.append 1 2;;
- : int = 3
2 Likes

Note that there aren’t any first-class modules being used in this code. You can read about first-class modules here. (I’m mentioning that mainly for the benefit of any other newbies who might come across this while searching, so they aren’t confused.)

2 Likes

Thanks a lot! The following code now compiles and works as expected! :100:

module List_semigroup (T: sig type t end) : Semigroup with type t = T.t list = struct
  type t = T.t list
  let append = (@)
end

let example =
  let l1 = [3; 1; 2] in
  let l2 = [0; 1; 2] in 
  let module L = List_semigroup(struct type t = int end) in
  L.append l1 l2

Do you know if it’s possible to reduce duplication here? Would be nice to avoid writing type t = T.t list inside with and inside struct.

Looks like my example from this post uses first-class modules now :sweat_smile:

This was actually my initial use case but I simplified the code in this question to avoid dealing with packing and unpacking modules.

Fair note though. I’ll update my title to reflect that I’m interested in functorised modules, not first-class.

It’s not possible to avoid duplication if you want to annotate the module type. However, since modules are structural and OCaml can infer them, you can remove the annotation. This works:

- module List_semigroup (T: sig type t end) : Semigroup with type t = T.t list = struct
+ module List_semigroup (T: sig type t end) = struct
  type t = T.t list
  let append = (@)
end

let example =
  let l1 = [3; 1; 2] in
  let l2 = [0; 1; 2] in 
  let module L = List_semigroup(struct type t = int end) in
  L.append l1 l2

You can’t avoid writing type t = T.t list though, since that’s a part of the module itself.

This still isn’t a first-class module. let module defines a regular module that’s just within another let scope (although that’s commonly used with FCMs). A FCM is a module that is “packed” into a value, so it could be stored in a data structure or passed as a function argument. It’s a relatively advanced feature that you probably won’t use right away when you’re new.

1 Like