I tried to compile an OCaml code with Dune but got the following error:
Error: No implementations provided for the following modules:
CallccBp referenced from bin/.CallccTest.eobjs/native/dune__exe__CallccTest.cmx
by executing the command : $ dune build
My project hierarchy is as follows:
callcc/
bin/
callccTest.ml
dune
[
(executable
(name callccTest)
(libraries CallccBp))
]
lib/
CallccBp.mli
dune
[
(library
(name CallccBp)
(modules_without_implementation CallccBp))
]
test/
callccTest.ml
dune [
(test
(name callccTest))
]
callcc.opam
dune-project
How can I solve this problem?
Looking at the discussion you had with octachron, let's start from the basics:
my_module.mli
File where you declare the signatures of your values, modules etc. Not mandatory, I'd advise not using them if you start with OCaml
my_module.ml
File where you implement your values, modules etc. Mandatory since these are the files that make your program run
Let's see this with a toy project:
.
├── bin
│ ├── dune
(executable
(name main)
)
│ └── main.ml
├── dune-project
├── lib
│ ├── dune
(library
(name my_module)
)
│ └── my_module.ml
└── program.opam
If I want to use values from my_module
in bin/main.ml
, I have to:
my_module.ml
(libraries my_module)
stanza in my bin/dune
fileMy_module.<name_of_value>
So this looks like:
.
├── bin
│ ├── dune
(executable
(name main)
(libraries my_module)
)
│ └── main.ml
let () =
let b = My_module.incr 3 in
Printf.printf "%d\n" b
├── dune-project
├── lib
│ ├── dune
│ └── my_module.ml
let incr a = a + 1
└── program.opam
Now, let's go back to your hierarchy:
callcc/
bin/
callccTest.ml
dune
[
(executable
(name callccTest)
(libraries CallccBp))
]
lib/
CallccBp.mli
dune
[
(library
(name CallccBp)
(modules_without_implementation CallccBp))
]
test/
callccTest.ml
dune [
(test
(name callccTest))
]
callcc.opam
dune-project
Everything looks fine except from the fact that CallccBp.mli
is just an interface, not an implementation. As a starter you could remove this file, create CallccBp.ml
filled with these two functions:
CallccBp.ml
let callcc = failwith "TODO"
let throw = failwith "TODO"
If you compile, dune should not complain and now all you'll have to do will be to provide a much useful implementation than failwith "TODO"
And if we go back to our toy project, to see why you'd want to have an mli file:
.
├── bin
│ ├── dune
(executable
(name main)
(libraries my_module)
)
│ └── main.ml
let () =
let b = My_module.incr 3 in
Printf.printf "%d\n" b
├── dune-project
├── lib
│ ├── dune
│ └── my_module.ml
let dummy _ = failwith "USELESS"
let incr a = a + 1
│ └── my_module.mli
val incr : int -> int
(** [incr d] will return [d] incremented by 1. Highly efficient. Trust me. *)
└── program.opam
I'll be able to use My_module.incr
in bin/main.ml
but not My_module.dummy
because it is not shown by the mli
file and thus not accessible outside of my_module.ml
. And as a bonus, my_module.mli
file is the entry point for a library user who doesn't want to know how it is implemented but just wants to use it knowing the available values, their types and, often, what they do from the comment.
The modules_without_implementation
stanza is for mli
files that don't need implementations, namely, type declarations files so modules looking like this:
AST.mli
type ('a, 'b) res = Ok of 'a | Error of 'b
type num = Int of int | Float of float
type person = {id : int; name : string; age : num}
And you can use them in another file like this:
file.ml
type t = {player1 : AST.person; player2 : AST.person}
let whos_older p1 p2 =
Printf.printf "%s is older\n"
(if p1.AST.age > p2.AST.age then p1.name else p2.name)
But that's not really useful when starting since, once again, I'd advise not touching mli
files in the beginning