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).

Źródła

  1. Partial Function Application is not Currying
  2. Signatures/types in functional programming (OCaml)
  3. Wikipedia: Currying
  4. OCaml Tutorial: partial function applications and currying