Przejdź do zawartości

OCaml/Rekurencja i iteracje

Z Wikibooks, biblioteki wolnych podręczników.

W językach funkcyjnych bardzo naturalną techniką jest rekurencja. Tworząc funkcje rekurencyjne dodajemy do definicji słówko "rec", bez niego funkcja nie będzie mogła wywoływać samej siebie. Dla przykładu prostą pętlę można rozwiązać w następujący sposób:

# let rec loop i =
	print_endline "Looping!";
	if (i>1) then loop (i-1)
;;
val loop : int -> unit = <fun>
# loop 3;;
Looping!
Looping!
Looping!
- : unit = ()

Funkcje "tail-recursive" to taki przypadek rekurencyjnych funkcji, w których ponowne wejście do funkcji znajduje się na ich końcu. Caml optymalizuje wszystkie takie przypadki generując assembler lub kod bajtowy w postaci normalnej iteracji. Dlatego stosowanie rekurencji do zapętlania programu nie jest obciążone żadnym większym zużyciem pamięci ani czasem potrzebnym na wywołanie funkcji jakby to miało miejsce w języku C i podobnych.

Istnieją również implementacje pętli for i while, lecz zazwyczaj się z nich nie korzysta. W Camlu nie istnieją wyrażenia break i continue. Jeśli jednak zajdzie potrzeba przerwania rekurencji lub pętli używany jest wyjątek Exit (zdefiniowany w Pervasives). W modułach zdefiniowane jest również wiele "iteratorów" -- funkcji służących do wykonywania cyklicznych operacji na np. elementach list, które opiszemy później.

Rekurencji ciąg dalszy:

# let rec silnia n =
	if n = 0 then 1 else n * silnia (n - 1);;

# let rec fib n =
	if (n <= 2) then
		1
	else	fib (n - 1) + fib (n - 2);;
val fib : int -> int = <fun>

Pierwsza funkcja, jak każdy się pewnie domyślił, pozwala obliczyć silnię z n. Druga zwraca n-ty wyraz ciągu Fibonacciego (licząc od zera). Widzimy tu, wiele opisanych wcześniej elementów. Ostatnie wyrażenie w "bloku kodu" zwraca wartość, oraz że każdy blok posiada jakiś typ. Jeśli np. w else zechcielibyśmy zwrócić typ float (zamiast "1" umieszczając tam "1.0") otrzymamy informację, że używane przez nas wyrażenie o typie float, jest używane jako int.

Funkcja fib nie jest funkcją "tail-recursive", dlatego możemy ją efektywniej napisać stosując funkcję pomocniczą, stworzoną za pomocą konstrukcji let ... in

# let fib x = 
	(* Funkcja pomocnicza *)
	let rec loop a b i =
		if i <= 2 then
			b
		else	let next = a + b in
			loop b next (i-1)
	in

	loop 1 1 x (* Jej wywołanie *)
;;
val fib : int -> int = <fun>

(* Wspomniana pętla for. Wyświetla początek 
 * ciągu fibonacciego: *)
# for i = 1 to 10 do
	print_int (fib i);
	print_newline ();
  done;;
1
1
2
3
5
8
(...)
- : unit = ()
(* Wyrażenie w pętli "for" musi zwracać typ unit. *)