2016-04-21 67 views
5

Ho un file molto molto lungo che contiene un insieme di funzioni ricorsive. La ricorsione è necessaria e anche il codice è efficace. Ma voglio solo dividere il file in 2 o più file per avere una migliore leggibilità. Ma non vedo come potremmo dividere let rec ... and ... in OCaml ...È possibile dividere un set di funzioni ricorsive in 2 file in OCaml?

Qualcuno sa se OCaml fornisce alcun meccanismo (ad esempio, un modo per specificare le interfacce o scrivere makefile) per farlo?

Il file molto molto lungo può sembrare:

let rec f1() = 
    ... 
    f7(); (* f7 is called only once by all the functions in the file *) 
    f2(); 
    ... 
and f2() = 
    ... 
    f1(); 
... 
and f7() = 
    ... 
    f1(); 
    ... 
+0

Potrebbe per favore fare un po 'di luce sul contesto di questo problema? Sono solo curioso: se è una sorta di codice generato automaticamente, allora perché preoccuparsi del tutto? –

+0

Non è un codice generato automaticamente, ho già scritto queste funzioni ... – SoftTimur

+1

Quanto è lungo quel file? Se è scritto manualmente, non lo dividerei (perché potresti perdere sia l'efficienza che la leggibilità). –

risposta

3

C'è un modo: functors mutuamente ricorsive. Vedi l'articolo this sulla compilazione separata in OCaml e nell'articolo ispiratore this che fornisce l'idea principale.

Ecco un esempio. Ho creato 4 interfaccia MLI-files:

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

e 3 di implementazione file:

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

Ecco il contenuto dei file di interfaccia:

(* 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 

e reciprocamente funtori ricorsive:

(* 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) 

Ed ora combinare i moduli:

(* 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 

Corporatura sequenza di comandi:

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 

I risultati dei test:

$ build.sh && ./a.out 
A: 4 
B: 3 
A: 2 
B: 1 
-------------- 
B: 4 
A: 3 
B: 2 
A: 1 
+0

Grazie per la risposta dettagliata ... – SoftTimur

2

Solo per riferimento, c'è un altro modo, senza funtori. Devi mettere le funzioni dall'altro modulo come parametro delle tue funzioni. Esempio: abbiamo even e odd definiti in modo ricorsivo, ma vogliamo che even sia in un modulo A e odd per essere in un modulo B.

primo file:

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

secondo file:

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

terzo 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 (è necessario utilizzare l'opzione -rectypes):

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

Non sono sicuro che lo consiglierei, ma funziona.

+1

Attenzione ai grandi e spietati '-rectypes'! Permette l'auto-applicazione (una cosa analoga è stata usata nella risposta), quindi compilerà anche il potente combinatore Y: 'let yf = (fun xa -> f (xx) a) (fun xa -> f (xx) a) '. –

+0

Hey Anton. Hai ragione. E per quanto riguarda 'let rec f x = f x'? Come posso evitarlo? ;-) –

+0

Basta usare 'let fx = fx' :) Per inciso (scusate, se è ovvio), è possibile creare funzioni ricorsive in OCaml senza' let rec' (perché OCaml non applica la rigida positività (ad es. Agda lo fa): 'tipo boxed_fun = | Box of (boxed_fun -> boxed_fun) lasciare nonTerminating (bf: boxed_fun): boxed_fun = partita bf con casella F -> f bf ciclo let = nonTerminating (Box nonTerminating)' –

Problemi correlati