Dla osób przybywających ze świata programowania imperatywnego zobaczenie jak wyglądają typy, które OCaml dedukuje dla definiowanych przez programistę funkcji, często rodzi pewne pytania i niejasności. Zawierająca strzałki notacja na pierwszy rzut oka nie wydaje się najbardziej oczywistym sposobem na wyrażenie np. faktu, że funkcja przyjmuje trzy argumenty typu int
i zwraca zmienną tego samego typu. Spójrzmy na poniższy przykład kodu wykonanego w top-levelu (aby zobaczyć typy dla funkcji zdefiniowanych w pliku można posłużyć się komendą ocamlc -i
, która wyświetli wydedukowane interfejsy):
W takim zapisie w żaden sposób nie jest wyróżniony zwracany typ (oprócz tego, że znajduje się na ostatniej pozycji). Okazuje się, że ma to swoje uzasadnienie, a jest nim currying (po polsku znany jako rozwijanie funkcji). Pojęcie to pochodzi od nazwiska amerykańskiego matematyka Haskella Curry’ego (a zgadnijcie jaki język zaczerpnął swą nazwę od jego imienia) i można wytłumaczyć je w ten sposób – mając funkcję przyjmującą N argumentów możemy rozwinąć ją w ciąg N funkcji, z których każda pobiera tylko jeden argument. Oto dwa równoznaczne wywołania stworzonej przez nas funkcji:
Na sygnaturę funkcji add
można zatem równie dobrze spojrzeć w ten sposób:
Wychodzi wtedy na to, że zwracanym typem nie jest int
, ale kolejna funkcja (która również zwraca funkcję i dopiero ta ostatnia funkcja zwraca liczbę). A skoro add
da się potraktować jako funkcję jednoargumentową, to całkiem legalnie można ją wywołać w ten sposób:
Jaki jest rezultat tego wywołania? Kolejna funkcja! W tym momencie zbliżamy się do praktycznego zastosowania curryingu, czyli tzw. częściowej aplikacji (ang. partial application). Pojęcie to niekiedy mylnie utożsamiane jest z rozwijaniem funkcji, jednak jego znaczenie, choć powiązane, jest odmienne. Odnosi się ono do sytuacji, w której część argumentów funkcji jest bindowana, przez co funkcja z N-argumentowej staje się funkcją M-argumentową, gdzie M < N. W praktyce wygląda to tak:
Poniżej zestawienie, systematyzujące poznane fakty i pokazujące jak wygląda typ naszej funkcji add
w zależności od liczby podanych doń argumentów:
Nie wiem jak dla Was, ale dla mnie powyższy przykład jest wystarczającym uzasadnieniem dziwnej strzałkowej notacji sygnatur funkcji, występującej w OCamlu. Nie tylko zresztą w OCamlu, bo z podobnym widokiem spotkamy się też w paru innych językach funkcyjnych, nie tylko tych z rodziny ML. Z zaprezentowanego w tym wpisie kodu można przy okazji wywnioskować co nieco o łączności operatorów: strzałka (->
) jest łączna prawostronnie (bo int -> int -> int
jest tym samym co int -> (int -> int)
), zaś aplikacja funkcji łączna lewostronnie (gdyż add 1 2
równoznaczne jest z (add 1) 2
).