Use a module's abstract type in other compilation unit

Greetings,
I’m having trouble compilling my program separated in 2 files. The idea is to make a small factory of Item, an abstract type defined in its module (Item).

item.ml:

module type Item =
    sig
        type item
        val make_Item : int -> string -> string -> Item
    end

module Item : ITEM =
    struct
        type Item = (int * string * string)
        let make_item id name description = (id, name, description)
    end

itemfactory.ml:

module type ItemFactory =
    sig
        val addItem : Item.item list -> int * string * string -> Item.item list
    end

module ItemFactory : ItemFactory =
    struct
        let isIdValid id = id > 0

        let addItem xs (id, name, description) =
            if (isIdValid id) then
				(Item.make_item id name description) :: xs
            else
                []
    end

Now, using the toplevel if I first #use “Item.ml” then #use “ItemFactory.ml”, it’ll work.
If I try to builld this manually, using make it won’t compile :

File "itemfactory.mli", line 4, characters 6-21:
4 |       Item.item list ->
          ^^^^^^^^^^^^^^^

Error: Unbound type constructor Item.item
make: *** [Makefile:45 : item.cmi] Erreur 2

Since from what I think I’ve understood, the #use statement in toplevel just append everything in the “context”, I think my program can compile but I’m most likely missing a step ?
I tried to use “open Item” at the top of itemfactory.ml it still gives me the “Unbound type constructor…” error. Replacing open with include gives “Unbound MODULE type”.

Please let me know how I can get this to compile, and if what I’m doing is wrong let me know too! Thanks.

The ideal way to manage this would be to let the dune build system do it for you. See https://dune.build/

In your case you would just need to lay out your project directory with the following files:

dune
dune-project
item.ml
item_factory.ml

Note that you are redundantly wrapping each module, this is not needed because OCaml already treats each file as a module. So in item.ml you can just do:

(* item.ml *)

type t = int * string * string
let make id name description = id, name, description

And as a best practice you can put the interface in its own file:

(* item.mli *)
type t
val make : int -> string -> string -> t

And similarly for item_factory.ml.

Once this is done building the project would be as simple as dune build. And running it in the toplevel would be just: dune utop.

1 Like

How exactly are you building it “manually”?

The error message mentions itemfactory.mli, but you didn’t mention this file in the description of the problem :slight_smile:

As already suggested, one possibility is to use a higher-level build system like Dune. However, if you want to compile by hand, it shouldn’t be too difficult either:

$ ocamlc -c item.ml # produces item.cmi, item.cmo
$ ocamlc -c itemfactory.ml # reads item.cmi, produces itemfactory.cmi, itemfactory.cmo
$ ocamlc -o prog.exe item.cmo itemfactory.cmo # produces final executable

This is assuming that there are no .mli files. If there are these should be built before the corresponding .ml files:

$ ocamlc -c item.mli # produces item.cmi
$ ocamlc -c itemfactory.mli # reads item.cmi, produces itemfactory.cmi
$ ocamlc -c item.ml # reads item.cmi, produces item.cmo
$ ocamlc -c itemfactory.ml # reads item.cmi, itemfactory.cmi, produces itemfactory.cmo
$ ocamlc -o prog.exe item.cmo itemfactory.cmo # produces final executable

Cheers,
Nicolas

1 Like

Hello, thanks both of you for your answers.
So yes sorry I’ve omitted the fact that I provided mli interfaces for both modules.
I currently use Make to buid all my project/excercices, but I’ve tried to switch to dune today and it still doesn’t build. I already tried to build manually like you’ve expained with ocamlc -c and linking the cmos it can’t build itemfactory.mli so…
I haven’t changed the source (sorry I kept my abstract type as “item” and not “t” but I got what you’re trying to tell me), thanks for explaining the redundancy too, I wouldn’t know haha.
I just got rid of my Makefile and setup my directory to be exploitable(hopefully) by dune, using a lib folder as it has no entry point for now, so this is where I’m sitting at now, back to the very same problem:

user@linuxworkstation ~/Projetcs/Ocaml/item$ ls -lR
.:
total 8
-rw-r--r-- 1 user user   28 20 juin  14:49 dune-project
drwxr-xr-x 2 user user 4096 20 juin  15:02 lib

./lib:
total 20
-rw-r--r-- 1 user user  61 20 juin  14:50 dune
-rw-r--r-- 1 user user 453 20 juin  14:54 itemfactory.ml
-rw-r--r-- 1 user user 166 20 juin  14:53 itemfactory.mli
-rw-r--r-- 1 user user 307 20 juin  14:50 item.ml
-rw-r--r-- 1 user user 149 20 juin  14:53 item.mli

user@linuxworkstation~/Projetcs/Ocaml/item$ cat dune-project 
(lang dune 2.7)
(name Item)
user@linuxworkstation~/Projects/Ocaml/agenda$ cat lib/dune 
(library
    (name Item_lib)
    (modules item itemfactory))
user@linuxworkstation~/Projetcs/Ocaml/item$ dune build
File "lib/itemfactory.mli", line 4, characters 18-27:
4 |     val addItem : Item.item list -> int * string * string -> Item.item list
                      ^^^^^^^^^
Error: Unbound type constructor Item.item
user@linuxworkstation~/Projetcs/Ocaml/item$ dune build
File "lib/itemfactory.mli", line 4, characters 18-27:
4 |     val addItem : Item.item list -> int * string * string -> Item.item list
                      ^^^^^^^^^
Error: Unbound type constructor Item.itemuser@linuxworkstation ~/Projetcs/Ocaml/item$ ls -lR
.:
total 8
-rw-r--r-- 1 user user   28 20 juin  14:49 dune-project
drwxr-xr-x 2 user user 4096 20 juin  15:02 lib

./lib:
total 20
-rw-r--r-- 1 user user  61 20 juin  14:50 dune
-rw-r--r-- 1 user user 453 20 juin  14:54 itemfactory.ml
-rw-r--r-- 1 user user 166 20 juin  14:53 itemfactory.mli
-rw-r--r-- 1 user user 307 20 juin  14:50 item.ml
-rw-r--r-- 1 user user 149 20 juin  14:53 item.mli

user@linuxworkstation~/Projetcs/Ocaml/item$ cat dune-project 
(lang dune 2.7)
(name Item)
user@linuxworkstation~/Projects/Ocaml/agenda$ cat lib/dune 
(library
    (name Item_lib)
    (modules item itemfactory))
user@linuxworkstation~/Projetcs/Ocaml/item$ dune build
File "lib/itemfactory.mli", line 4, characters 18-27:
4 |     val addItem : Item.item list -> int * string * string -> Item.item list
                      ^^^^^^^^^
Error: Unbound type constructor Item.item
user@linuxworkstation~/Projetcs/Ocaml/item$ dune build
File "lib/itemfactory.mli", line 4, characters 18-27:
4 |     val addItem : Item.item list -> int * string * string -> Item.item list
                      ^^^^^^^^^
Error: Unbound type constructor Item.item

Here is item.mli:

module type ITEM =
  sig
    type item
    val make_item : int -> string -> string -> item
  end
  
module Item : ITEM

And itemfactory.mli:

module type ITEMFACTORY =
  sig
    val addItem : Item.item list -> int * string * string -> Item.item list
  end
  
module Itemfactory : ITEMFACTORY

Thanks

Hi, if you try out my suggestion, it will compile. Once you have compiled, you can try modifying it to your liking and see at what point it breaks.

1 Like

Just tried it works! Thanks so much!
I’m learning with a quite old ressources (2011 courses haha).
So I suppose this syntax (where you write module type etc) is not used anymore ? Or maybe for functors only ? I’ll finish this chapter and try to find recent ressources, thanks for your patience !

In addition you don’t need to specify modules, dune will find your modules automatically. That way when you add a new module, you don’t have to explicitly add it to your build file.

1 Like

It is used but only if you want to define e.g. modules inside a module. Each ml file is an own module, so you can write to a file a.ml

module B = struct
end

module C = struct
end

And then you have a module A which has a module A.B and another module A.C.

2 Likes

You can use that syntax if you want, it’s just that it’s redundant in this case. And then you end up with a nested module so you would have to write Item.Item.item to access the type. It’s much simpler to have just a single level of module in this case and have a type t so you just have to write Item.t.

1 Like

Dune file fixed, code cleaned a bit and now I understand the “layer” of modules thanks all for the help apreciate it!

1 Like