OCaml/Funkcje jako wartości

Przed nami rozdział opisujący główne cechy, które Caml zawdzięcza swoim funkcyjnym korzeniom. Ich dokładnie rozumienie często nie jest niezbędne do tworzenia prostych programów, ale z pewnością może to ułatwić.

(* Tworzymy funkcję pobierającą 3 argumenty *)
# let add_three a b c = a + b + c;;
val add_three : int -> int -> int -> int = <fun>
(* Jeśli wywołamy ją z mniejszą liczbą argumentów, zwróci
 * nam nową funkcję "odejmując" z typu jedno "int ->" 
 * z lewej strony *)
# add_three 1;;
- : int -> int -> int = <fun>
# add_three 1 2;;
- : int -> int = <fun>
# add_three 1 2 3;;
- : int = 6

(* Może więc przy zwykłym wywoływaniu
 * dzieje się tak samo? *)
# (((add_three 1) 2) 3);;
- : int = 6

Proces sprowadzania funkcji wieloargumentowych do jednoargumentowych w matematyce i informatyce (rachunku lambda) nazywa się "currying". Możemy też stworzyć funkcję z paru funkcji jednoargumentowych:

# let add_three = fun a -> fun b -> fun c -> a + b + c;;
val add_three : int -> int -> int -> int = <fun>

Nie znam wprawdzie bezpośredniego zastosowania takiej metody deklarowania funkcji, lecz słowa kluczowego "fun" (lub "function") używa się dość często. Wtedy kiedy chcemy podkreślić, że typem zwracanym przez funkcję jest inna funkcja, jeśli chcemy stworzyć "funkcję anonimową", lub gdy chcemy stworzyć funkcję, która ma przeprowadzać dopasowywanie (o tym za chwilkę).

# let deriv f dx = fun x -> ( f(x +. dx) -. f(x) ) /. dx;;
val deriv : (float -> float)->float->float->float = <fun>
 
# let sin' = deriv sin 1e-6;;
val sin' : float -> float = <fun>

# sin' 3.141592653589793;;
- : float = -1.00000000013961143

W tym przykładzie stworzyliśmy funkcję, która dla dowolnej podanej funkcji typu float -> float zwraca funkcję (float -> float), która oblicza przybliżenie jej matematycznej pochodnej. Definicja funkcji deriv w tym przykładzie używała słowa "fun". Możemy zapisać ją bez użycia tego słowa kluczowego w sposób absolutnie równoważny:

# let deriv f dx x = ( f(x +. dx) -. f(x) ) /. dx;;
val deriv : (float -> float)->float->float->float = <fun>

Wydaje mi się, że ta metoda mniej wskazuje na sposób w jaki funkcja powinna być wywoływana. Wcześniej miała 2 argumenty i zwracała funkcję. Teraz ma 3 i zwraca wartość pochodnej w punkcie... chyba, że zastosujemy częściową aplikację.

Można również prosto zdefiniować funkcję służącą do składania funkcji:

# let compose f g = function x -> f(g(x));;
val compose : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b = <fun>
 
# let sqrt_cos = compose sqrt cos;;
val sqrt_cos : float -> float = <fun>

Znane nam już operacje dodawania, odejmowania, mnożenia i dzielenia to również zwykłe dwuargumentowe funkcje zdefiniowane w module Pervasives. Programista bardzo łatwo może definiować swoje własne operatory. Właściwie tak jak w typowo obiektowych językach (Ruby), gdzie wszystko jest obiektem tutaj praktycznie wszystko jest funkcją.