Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to split a set of recursive functions into 2 files in OCaml?

I have a very very long file which contains a set of recursive functions. The recursion is necessary and the code is effective as well. But I just want to split the file into 2 or more files to have a better readability. But I don't see how we could split let rec ... and ... in OCaml...

Does anyone know if OCaml provides any mechanism (e.g., a way to specify interfaces or write makefile) to do so?

The very very long file may look like:

let rec f1 () =
  ...
  f7 (); (* f7 is called only once by all the functions in the file *)
  f2 ();
  ...
and f2 () =
  ...
  f1 ();
...
and f7 () =
  ...
  f1 (); 
  ...
like image 814
SoftTimur Avatar asked Apr 21 '16 17:04

SoftTimur


2 Answers

There is a way: mutually recursive functors. See this great article on separate compilation in OCaml and this inspiring article which provides the main idea.

Here is an example. I've created 4 interface mli-files:

$ ls *.mli
Make_moduleA.mli  Make_moduleB.mli  ModuleA.mli  ModuleB.mli

and 3 implementation files:

$ ls *.ml
Make_moduleA.ml  Make_moduleB.ml  main.ml

Here is the interface files' contents:

(* ModuleA.mli *)
module type ModuleA = sig
   val fa : int -> unit
end

(* ModuleB.mli *)
module type ModuleB = sig
   val fb : int -> unit
end

(* Make_moduleA.mli *)
open ModuleA
open ModuleB
module type Make_moduleA_sig =
  functor (Mb : ModuleB) ->
    sig
      val fa : int -> unit
    end
module Make_moduleA : Make_moduleA_sig

(* Make_moduleB.mli *)
open ModuleA
open ModuleB
module type Make_moduleB_sig =
  functor (Ma : ModuleA) ->
    sig
      val fb : int -> unit
    end
module Make_moduleB : Make_moduleB_sig

And mutually recursive functors:

(* Make_moduleA.ml *)
open ModuleA
open ModuleB    
module type Make_moduleA_sig =
  functor (Mb : ModuleB) ->
    sig
      val fa : int -> unit
    end
module Make_moduleA_impl =
  functor (Mb : ModuleB) ->
    struct
      let rec fa (n : int) =
        if n > 0 then
          (Printf.printf "A: %d\n" n;
           Mb.fb (n - 1))
    end
module Make_moduleA = (Make_moduleA_impl : Make_moduleA_sig)

(* Make_moduleB.ml *)
open ModuleA
open ModuleB    
module type Make_moduleB_sig =
  functor (Ma : ModuleA) ->
    sig
      val fb : int -> unit
    end    
module Make_moduleB_impl =
  functor (Ma : ModuleA) ->
    struct
      let rec fb (n : int) =
        if n > 0 then
          (Printf.printf "B: %d\n" n;
           Ma.fa (n - 1))
    end    
module Make_moduleB = (Make_moduleB_impl : Make_moduleB_sig)

And now let's combine the modules:

(* main.ml *)
open ModuleA
open ModuleB
open Make_moduleA
open Make_moduleB

module rec MAimpl : ModuleA = Make_moduleA(MBimpl)
and MBimpl : ModuleB = Make_moduleB(MAimpl)

let _ =     (* just a small test *)
  MAimpl.fa 4;
  print_endline "--------------";
  MBimpl.fb 4

Build command sequence:

ocamlc -c ModuleA.mli
ocamlc -c ModuleB.mli
ocamlc -c Make_moduleA.mli
ocamlc -c Make_moduleB.mli

ocamlc -c Make_moduleA.ml
ocamlc -c Make_moduleB.ml
ocamlc -c main.ml

ocamlc Make_moduleA.cmo Make_moduleB.cmo main.cmo

Test results:

$ build.sh && ./a.out
A: 4
B: 3
A: 2
B: 1
--------------
B: 4
A: 3
B: 2
A: 1
like image 175
Anton Trunov Avatar answered Oct 21 '22 01:10

Anton Trunov


Just for reference, there is another way, without functors. You have to put functions from the other module as parameter of your functions. Example : we have even and odd defined recursively, but we want even to be in a module A and odd to be in a module B.

First file :

(* A.ml *)
let rec even odd n =  
  if n=0
  then true
  else 
    if n=1 
    then false
    else (odd even) (n-1) ;;

Second file :

(* B.ml *)
let rec odd even n =  
if n=1
  then true
  else 
    if n=0 
    then false
    else even odd (n-1) ;;

Third file :

(* C.ml *)
let even = A.even B.odd 
and odd  = B.odd A.even ;;
print_endline (if even 5 then  "TRUE" else "FALSE") ;;
print_endline (if odd 5 then  "TRUE" else "FALSE") ;;

Compilation (you must use the option -rectypes) :

ocamlc -rectypes -c A.ml 
ocamlc -rectypes -c B.ml
ocamlc -rectypes -c C.ml
ocamlc -rectypes  A.cmo B.cmo C.cmo 

I'm not sure I would recommand that, but it works.

like image 21
Julien C. Avatar answered Oct 20 '22 23:10

Julien C.