OCaml

język programowania

OCaml (wcześniej jako Objective Caml) – wieloparadygmatowy język programowania oraz implementacja tego języka w postaci zestawu narzędzi i bibliotek. Jest, oprócz Caml Light, główną implementacją języka Caml. OCaml został stworzony przez Xaviera Leroya, Jérôme Vouillon, Damien Doligeza, Didier Rémy i innych w 1996 roku, kiedy to Caml Light został poszerzony o system obiektów i natywny kompilator.

OCaml
logo
Paradygmat

Funkcyjny, Obiektowy, Imperatywny

Typowanie

Silne, Statyczne z inferencją

Aktualna wersja stabilna

5.2.0
(13 maja 2024) [±]

Twórca

Xavier Leroy, Damien Doligez, Jérôme Vouillon

Licencja

QPL

Platforma sprzętowa

Wieloplatformowy

Platforma systemowa

Wieloplatformowy

Strona internetowa

OCaml wspiera równie dobrze programowanie funkcyjne, obiektowe, jak i imperatywne.

Nadaje się do pisania dużych programów przemysłowych ze względu na silny system modułów, dostępne programowanie obiektowe, szybki natywny kompilator oraz szczególnie dobre wsparcie dla programowania funkcyjnego.

Jest wolnym oprogramowaniem tworzonym we francuskim akademickim instytucie badawczym INRIA.

OCaml wywodzi się z rodziny języków Meta Language, podobnie jak Standard ML.

Programy napisane w Ocamlu zajmują czołowe miejsca w ICFP Programming Contest.

Narzędzia

edytuj

OCaml składa się z następujących narzędzi:

  • ocaml – interpreter (tzw. REPL) pozwalający na interaktywne wpisywanie wyrażeń i konstrukcji języka.
  • ocamlc – kompilator do bytecodu. Pozwala na pisanie przenośnych programów, które jednak są wolniejsze niż programy skompilowane natywnym kompilatorem.
  • ocamlrun – interpreter bytecodu i system runtime. Pozwala na wykonanie pliku skompilowanego przez ocamlc.
  • ocamlopt – optymalizujący, natywny kompilator. Generuje bardzo szybki kod na wiele platform.
  • ocamldep – analizator zależności między modułami. Głównym zadaniem jest tworzenie plików zawierających informacje o zależnościach dla innych narzędzi.
  • ocamldoc – generator dokumentacji do bibliotek, podobny do Doxygena dla C/C++. Wszystkie biblioteki dostępne do OCamla powinny mieć dokumentację API utworzoną za pomocą tego narzędzia.
  • ocamlbuild – program pomocny do budowania całych projektów.
  • ocamllex – generator analizatorów leksykograficznych.
  • ocamlyac – generator parserów LALR(1).
  • ocamlcp – profiler, pomocny przy analizie szybkości działania programów.
  • camlp4 – pre-procesor programów napisanych w OCamlu. Pozwala na poszerzanie składni, generację i analizę kodu w OCamlu. Operuje bezpośrednio na drzewie składni.
  • ocamlfind – manager bibliotek. Umożliwia wygodne zarządzanie instalacją pakietów. Mimo że nie występuje w standardowej dystrybucji OCamla jest zaliczany do jednych z ważniejszych narzędzi.

Używanie Ocamla z poziomu powłoki

edytuj

Uwaga: Informacje te odnoszą się do systemów uniksowych. W innych systemach niektóre czynności należy wykonywać w inny sposób.

Pliki źródłowe ocamla mają rozszerzenie ml, pliki z sygnaturami – odpowiednik plików nagłówkowych w C – rozszerzenie mli. Nie ma tu jednak żadnego preprocesora i literalnego włączania nagłówków – pliki sygnaturowe są najzwyczajniej kompilowane.

Zwykle nie ma potrzeby tworzenia osobnych plików sygnaturowych i OCaml automatycznie generuje sygnatury na podstawie plików ml.

Istnieją trzy sposoby wykonywania programów napisanych w języku OCaml:

  • za pomocą interpretera – komenda ocaml. Dogodne do pisania prostych skryptów czy testowania.
  • kompilacja do binarnej postaci interpretowanej – komenda ocamlc. Program ten przetwarza kod źródłowy na byte code który może zostać uruchomiony przez system uruchomieniowy zawierający również interpreter. Instrukcje są podobne do wielu innych wirtualnych maszyn (jak JVM, .NET czy Dalvik) poza faktem że odbywa się bezpośrednia interpretacja, a nie translacja w locie (tzw. JIT). ocamlrun to narzędzie który wykonuje instrukcje zapisane w pliku wykonywalnym. Pliki binarne są agnostyczne względem platformy, dlatego głównym ich zastosowaniem jest tzw. bootstrapowanie samego kompilatora OCamla.
  • kompilacja do kodu maszynowego – komenda ocamlopt. Tworzy bardzo szybki program, który nie wymaga żadnych zewnętrznych bibliotek oprócz libc.

W wyniku działania kompilatorów Ocamla powstają pliki:

  • cmi – skompilowane pliki sygnaturowe, takie same w ocamlc i ocamlopt
  • cmo – pliki obiektów dla ocamlc
  • cma – bibliotek dla ocamlc
  • o – zwykłe pliki obiektowe, tworzone przez ocamlopt
  • cmx – pliki obiektowe z dodatkowymi informacjami na temat modułów służące do optymalizacji, tworzone przez ocamlopt.
  • cmxa – biblioteki dla ocamlopt.
  • pliki wykonywalne (bez rozszerzenia):
    • pliki skryptowe shebang ocamlrun generowane przez ocamlc
    • binarne pliki wykonywalne (na Linuksie typu ELF) generowane przez ocamlopt

System typów

edytuj

Jądro systemu typów opiera się na polimorficznie typowanym rachunku lambda z inferencją typów algorytmem unifikacji Hindley-Milner.

OCaml jest silnie typowanym językiem.

Nie dopuszcza żadnych automatycznych konwersji, czy przeciążania funkcji czy nawet przeciążonych operatorów infix dla różnych typów numerycznych.

Zaletą tego jest że algorytm inferencji jest prosty i efektywny, wadą natomiast jest czasem użycie, gdzieniegdzie, pomocniczych funkcji – co w praktyce nie jest problemem i kod w OCamlu zazwyczaj i tak jest bardziej zwięzły niż w innych językach.

Inne języki funkcyjne, jak Haskell, posiadają mechanizm przeciążania przez tzw. type classes – które pełnią podobne role jak moduły i funktory w OCamlu. Umożliwia to inferencje typów wraz z udogodnieniami przeciążania, z umiarkowaną ceną dodatkowych adnotacji sygnatur funkcji.

Własnością bazowego system typów jest brak wymogu jakichkolwiek adnotacji. Adnotacje natomiast występują w celu generacji dokumentacji przez ocamldoc bądź przy bardziej zaawansowanych użyciach systemu wychodzącego poza pierwotny system typów ML, jak funktory, obiekty, moduły opakowane, polimorficzna rekursja czy GADTs.

Składnia

edytuj

Syntaktycznie bazowy język OCaml ma bardzo prostą, zwięzłą, ale zarazem bardzo praktyczną składnie składającą się z następujących komponentów:

  • definicje typów
  • definicje wartości
  • definicje modułów
  • deklaracje typów i wartości w sygnaturach modułów
  • adnotacje typów

oprócz tego występują między innymi elementy jak:

  • komentarze między (* i *) które mogą być zagnieżdżone
  • stałe stringów które mogą być kilkuliniowe
  • stałe list
  • stałe tablic

Deklaracje typów

edytuj
  • deklaracja typów przez słowo kluczowe type – w przeciwieństwie do Standard ML, OCaml nie ma rozróżnienia między deklaracjami nowych typów a algebraicznymi typami (przez datatype w SML), przykłady:
(* Aliasy typu *)
type numer = int
type para = string * int
type lista_int = int list
(* Rekord *)
type punkt = { x : float; y : float }
(* Rekord polimorficzny *)
type ('a, 'b) nazwana_para = { first : 'a; second : 'b }
(* Warianty, czy alternatywy (polimorficzne) *)
type 'a drzewo =
      Drzewo of 'a drzewo * 'a drzewo
    | Lisc of 'a
(* Typ abstrakcyjny *)
type abstrakcyjny

Jak widać typy mogą być polimorficzne, czyli mogą być parametryzowane przez inny typ poprzez zmienne typu (jak w przykładzie 'a albo 'b). Zmienna typu zostanie podstawiona automatycznie przez inferencje typów.

Definicje wartości

edytuj

Wartości są definiowane przez konstrukcje let która nadaje nazwę wartości czyniąc ją zmienną.

(* Globalna wartosc *)
let numer = 42
(* Definicje tej samej funkcji *)
let dodaj x y = x + y
let dodaj = fun x y -> x + y
let dodaj = fun x -> fun y -> x + y
(* Bardziej skomplikowana funkcja, zmienne lokalne *)
let pitagoras p1 p2 =
   let roznica = { x = p2.x - p1.x; y = p2.y - p1.y } in
   roznica.x * roznica.x + roznica.y * roznica.y
(* Definicja instancji drzewa *)
let drzewo = Drzewo (Drzewo (Lisc 1, Lisc 2), Lisc 3)
(* Lista *)
let lista_int = [1;2;3;4;5;6]
(* Rekord *)
let punkt = { x = 1.; y = 2. }
(* Polimorficzny rekord *)
let nazwana_para = { first = 1.0; second = "Ala ma kota" }
let nazwana_para = { first = [1;2;3]; second = 42 }

Czyli liczby całkowite. Operacje na nich to m.in. +, -, *, /.

let x = 2 + 2 * 2;;
print_int x;;

Liczby zmiennoprzecinkowe mają osobny zestaw operacji, co zmniejsza znacznie czytelność, ale jest konieczne ze względu na sposób działania systemu inferencji typów.

Operacje te zwykle kończą się kropką, np. +., -., *., /..

let y = 2.0 +. 2.0 *. 2.0;;
print_float y;;

Pojedyncze znaki umieszcza się w pojedynczym cudzysłowie:

let c = '\n';;
print_char c;;

Do zamieniania znaków na ich wartości numeryczne i na odwrót służą int_of_char oraz char_of_int.

string

edytuj

Łańcuchy tekstowe umieszcza się w podwójnym cudzysłowie:

let s = "Ala ma kota\n";;
print_string s;;

Wartości logiczne – true i false. Operacje to not, ||, && itd.

Typ pusty, wartość tylko ().

Oraz na typach pochodnych takich jak:

Listy elementów danego typu

edytuj

Lista elementów danego typu to 'a list, np. [1; 2; 3] to lista typu int list, a [2.71; 3.14; 6.28] to lista typu float list.

Krotka

edytuj

Krotka to zestaw ustalonej liczby wartości o przyporządkowanych im na stałe, lecz niekoniecznie tych samych, typach zmiennych. Krotką jest np. para (2, "napis"), czy trójka (3, 2, 3.14). Branie krotek w nawiasy nie jest konieczne, lecz zwiększa czytelność programu.

Alternatywy

edytuj

Alternatywa to zestaw konstruktorów, które mogą być parametryzowane (wtedy typ ma podwartości równe wszystkim możliwym wartościom parametru) bądź też nie (istnieje tylko jedna wartość z takim konstruktorem). Jeśli potrzebny jest konstruktor, który przyjmuje więcej niż jeden parametr, używa się krotki.

Na przykład zdefiniujmy typ foo mający dwa konstruktory – Foo o parametrze int i Bar o parametrze string:

type foo = Foo of int | Bar of string;;

let print_foo = function
    Foo n -> print_int n
  | Bar s -> print_string s
;;

print_foo (Foo 2);;
print_foo (Bar "Napis")

Przykładem predefiniowanej polimorficznej alternatywy jest typ 'a option. Np. dla typu int option poprawnymi wartościami są None i Some 4.

Przykładowy kod

edytuj
(* komentarz *)
let rec fib n =
  if n < 2
    then n
    else fib (n-1) + fib (n-2)
;;

(* inny sposób, wykorzystujący dopasowanie do wzorca *)
let rec fibb = function
  | 0 -> 0
  | 1 -> 1
  | n -> fibb (n-1) + fibb(n-2)
;;

print_string "Hello, world !\n";;
print_int (fib (2+2*2));;
print_newline ();;


Linki zewnętrzne

edytuj