Splitting submodules with Dune

Greetings,

Second post in here I’ve yet another problem understanding the module system, and how dune make them work.
My program contains simple functor called “Trip”, taking two modules as parametric modules : “Train” & “Road”. The “Trip” code is located in my /lib folder with the following dune file :

(library
(modules trip date)
(name trainmanager))

I’ve been told that it was unecessary to put the “modules” stanza, but still, it might help everyone understand.
So /lib contains date.ml, which contains multiple function regarding date manipulation, annd trip contains, the “Train”, “Road” modules, and the “Journey” functor.
My project works well this way, the library is called “trainmanager”, so in bin/main.ml I access my lib modules this way :

module TrainImpl = Trainmanager.Trip.Train
module Roadmpl = Trainmanager.Trip.Road
module Journey = Trainmanager.Trip.MakeJourney(TrainImpl)(RoaImpl)

And I can use them, it has helped me understand the functors principle, but, now I’d like to split all the trip.ml’s submodules into different files and have the following structure:

lib/ :
dune
date.ml
trip.ml
trip.mli
train.ml
train.mli
road.ml
road.mli

But I haven’t been able to make it work. I stumble upon this problem:

174 | module Journey = Trainmanager.Trip.MakeJourney(TrainImpl)(RoadImpl)
                                                     ^^^^^^^^^
Error: Signature mismatch:
       ...
       The value `get_associated_network' is required but not provided
       File "trainmanager/lib/train.mli", line 10, characters 2-52:
         Expected declaration
       The value `check_station_network' is required but not provided
       File "trainmanager/lib/train.mli", line 9, characters 2-59:
         Expected declaration
       The value `getTrainSpeed' is required but not provided
       File "trainmanager/lib/train.mli", line 8, characters 2-37:
         Expected declaration
       The value `get_speed' is required but not provided
       File "trainmanager/lib/train.mli", line 7, characters 2-28:
         Expected declaration
       The value `get_model' is required but not provided
       File "trainmanager/lib/train.mli", line 6, characters 2-29:
         Expected declaration
       The value `create' is required but not provided
       File "trainmanager/lib/train.mli", line 5, characters 2-35:
         Expected declaration
       The exception `Station_not_in_network' is required but not provided
       The exception `Unknow_train_type' is required but not provided
       The type `t' is required but not provided
       File "trainmanager/lib/train.mli", line 2, characters 2-8:
         Expected declaration

I changed the way TrainImpl and RoadImpl are assigned :

module TrainImpl = Trainmanager.Train
module RoadImpl = Trainmanager.Road

And the source files are organized the way I described above.
I’ve basically moved all the signature part of both Road & Train in their .mli files and the struct part in their .ml files but that just won’t work, what am I missing could anyone help ? I can always provides more details, code and please, tell me if the design is just wrong etc, thanks.

What you are trying to do is entirely reasonable, but it looks like something is pointing to the wrong place. If this code is available on a public repo, that would probably be the quickest way for people to diagnose the problem.

Hey, thanks for coming back at me.
I’ve upload the project on this public repo (made a one shot account on GH):

So I’ve made two commits, the first one is functional much like I’ve explained the second just won’t build, with the same error :

138 | module Journey = Trainmanager.Trip.MakeJourney(TrainImpl)(RoadImpl)
                                                     ^^^^^^^^^
Error: Signature mismatch:
       ...
       The value `get_associated_network' is required but not provided
       File "lib/train.mli", line 11, characters 4-54: Expected declaration
       The value `check_station_network' is required but not provided
       File "lib/train.mli", line 10, characters 4-61: Expected declaration
       The value `getTrainSpeed' is required but not provided
       File "lib/train.mli", line 9, characters 4-39: Expected declaration
       The value `get_speed' is required but not provided
       File "lib/train.mli", line 8, characters 4-30: Expected declaration
       The value `get_model' is required but not provided
       File "lib/train.mli", line 7, characters 4-31: Expected declaration
       The value `create' is required but not provided
       File "lib/train.mli", line 6, characters 4-37: Expected declaration
       The exception `Station_not_in_network' is required but not provided
       The exception `Unknow_train_type' is required but not provided
       The type `t' is required but not provided
       File "lib/train.mli", line 3, characters 4-10: Expected declaration

I’ve also had trouble using a function from Train inside Road (didn’t remember that when posting) so you can see this block in road.ml :

    let durationInMinutes = (float_of_int distance /. 210.0) *. 60.0 in
    let integerDurationInMinutes = int_of_float (Float.floor durationInMinutes) in
    let arrivalTimeInMinutes = startTimeInMinutes + integerDurationInMinutes in
    let arrivalTimeHoursPart = (arrivalTimeInMinutes / 60) mod 24 in
    let arrivalTimeMinutesPart = arrivalTimeInMinutes mod 60 in
    string_of_int arrivalTimeHoursPart ^ ":" ^ string_of_int arrivalTimeMinutesPart ^ trainType

When the code I want is :

    let durationInMinutes = (float_of_int distance /. (Train.getTrainSpeed trainType)) *. 60.0 in
    let integerDurationInMinutes = int_of_float (Float.floor durationInMinutes) in
    let arrivalTimeInMinutes = startTimeInMinutes + integerDurationInMinutes in
    let arrivalTimeHoursPart = (arrivalTimeInMinutes / 60) mod 24 in
    let arrivalTimeMinutesPart = arrivalTimeInMinutes mod 60 in
    string_of_int arrivalTimeHoursPart ^ ":" ^ string_of_int arrivalTimeMinutesPart

I did that for the sake of progressing in the compilation process. Only to discover more problems.

I’m sorry the code is messy haha, I really want to start cleaning it by first splitting my modules.
Thanks!

EDIT :
3rd commits now build with separate files ! Yay, but it’s using wrapped modules so I gotta access Road through Road.Road much like my first question in the forum, I should know the fix, but when I don’t wrap my code under the module … = struct… syntax, I get bunch of unbound value, don’t know why…

There’s generally no need to define a module type for every single module. The main purpose of module types is to reuse the signatures (e.g. for functors). In OCaml, every ml/mli pair compiles to a module and the mli is used as its signature, so essentially the mli is the module’s module type.

I do see that you are using these module types for a functor signature, although it’s not clear to me what benefit the functor serves. These module types are tightly coupled with a specific implementation, so I’m not sure how feasible it would be to try using the functor to create different “journeys” based on different trains, roads, etc. If you only intend to use the functor once, then it doesn’t need to be a functor, and you can greatly simplify this design.

Your code will be more manageable if you:

  • Remove the module types from each module and place the signature that was in each module type directly in each mli.
  • Remove the nested modules (module ... = struct ...) so everything is just a file-level module.
  • Remove the functor (turn it into a regular module without parameters).

If you do need to use a functor, I would suggest:

  • Defining its module type parameters in the same module that defines the functor, which will keep them from being coupled with a specific implementation.
  • Keep the module types simple and abstract.
  • Have more than one implementation for each module type that you can test it on.
1 Like

Hopefully this will prove useful to you (and maybe others). I’ve tried to take the basic idea behind your code, strip it back to an absolute minimum and then split it up in three different ways.

It should make experimenting to figure out what works and what doesn’t reasonably straightforward.

HTH

2 Likes

Hey thanks you so much both you, appreciate it a lot !
Gonna clone this and read it thoroughly, I’ve got the greater idea of module type etc but now will look on how to use them.
Thanks!