2016-04-21 10 views
5

Mam bardzo bardzo długi plik, który zawiera zestaw funkcji rekursywnych. Rekurencja jest konieczna, a kod również jest skuteczny. Ale chcę podzielić plik na 2 lub więcej plików, aby uzyskać lepszą czytelność. Ale nie widzę, jak możemy podzielić let rec ... and ... w OCaml ...Czy jest możliwe podzielenie zestawu funkcji rekursywnych na 2 pliki w OCaml?

Czy ktoś wie, czy OCaml zapewnia dowolny mechanizm (np. Sposób określania interfejsów lub pisać makefile), aby to zrobić?

Sama bardzo długo plik może wyglądać następująco:

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

Czy możesz rzucić nieco światła na kontekst tego problemu? Jestem ciekawy: jeśli to jakiś rodzaj automatycznie generowanego kodu, to po co zawracać sobie głowę? –

+0

To nie jest automatycznie generowany kod, sam napisałem te funkcje ... – SoftTimur

+1

Jak długo to jest plik? Jeśli jest napisane ręcznie, nie rozdzieliłbym go (ponieważ możesz stracić zarówno wydajność, jak i czytelność). –

Odpowiedz

3

Jest na to sposób: wzajemnie rekurencyjne funktory. Zobacz artykuł this świetny artykuł na osobnej kompilacji w OCaml i this inspirujący artykuł, który stanowi główny pomysł.

Oto przykład. Utworzyłem 4 interfejsów MLI-pliki:

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

i 3 wdrożeniowe pliki:

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

Oto treść Plik interfejsu:

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

i wzajemnie rekurencyjne funktory:

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

A teraz łączyć moduły:

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

Budowa sekwencji poleceń:

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 

Wyniki badań:

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

Dziękuję za szczegółową odpowiedź ... – SoftTimur

2

tylko w celach informacyjnych, istnieje inny sposób, bez funktorów. Musisz ustawić funkcje z drugiego modułu jako parametr swoich funkcji. Przykład: mamy zdefiniowane even i odd zdefiniowane rekursywnie, ale chcemy, aby even był w module A i odd, aby być w module B.

pierwszego pliku:

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

Drugi plik:

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

Trzeci plik:

(* 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 (należy użyć opcji -rectypes):

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

Nie jestem pewien, czy poleciłbym to, ale działa.

+1

Uważaj na wielkie i bezwzględne '-rectypes'! Pozwala to na samoaplikowanie (analogiczna rzecz została użyta w odpowiedzi), więc potężny kombinator Y również się skompiluje: 'let yf = (fun xa -> f (xx) a) (fun xa -> f (xx) a) '. –

+0

Hej Anton. Masz rację. A co powiesz na "let rec f x = f x"? Jak mogę tego uniknąć? ;-) –

+0

Po prostu użyj 'let fx = fx' :) Nawiasem mówiąc (przepraszam, jeśli to oczywiste), możesz tworzyć funkcje rekursywne w OCaml bez' let rec' (ponieważ OCaml nie wymusza ścisłej pozytywności (np. Agda to robi): 'typ boxed_fun = | Box (boxed_fun -> boxed_fun) niech nonTerminating (BF: boxed_fun): boxed_fun = mecz bf z pole f -> f bf let pętli = nonTerminating (Box nonTerminating)' –

Powiązane problemy