OCaml - elemente de limbaj (recapitulare)

Funcții

În ML putem defini expresii de tip funcție: fun x -> x + 3 . Acestea pot fi aplicate ca atare: (fun x -> x + 3) 2 este 5, sau transmise ca parametru: List.map (fun x -> x + 3) [1;2;4] e lista [4; 5; 8].

Definițiile let f x = x + 3 și let f = fun x -> x + 3 sunt echivalente (prima e mai scurtă și cu sintaxă mai familiară).

ML are, strict vorbind, doar funcții cu un singur parametru, ceea ce e suficient. O funcție "cu mai mulți parametri" let add x y = x + y e de fapt doar o scriere simplificată pentru let add = fun x -> fun y -> x + y . S-a definit de fapt o funcție care, aplicată primului parametru x returnează o funcție, care aplicată încă unui parametru y produce suma. Putem deci aplica parțial o funcție la mai puțini parametri decât cei din definiție, și obținem o funcție (care se poate apoi aplica mai departe).

let add3 = add 3  (* adica fun y -> 3 + y *)
add3 2  (* rezultă 3 + 2 = 5 *)
Aplicarea funcției (apelul de funcție) e asociativ la stânga, deci add 2 3 înseamnă (add 2) 3, consistent cu cele explicate mai sus: se evaluează add 2, care returnează o funcție, care e apoi aplicată lui 3.

Notația obișnuită din matematică f (x, y) = x + y produce în ML altceva, o funcție cu un singur parametru, care e o pereche (în notația uzuală cu paranteze și virgulă). Funcția add de mai sus are tipul int -> int -> int (funcție de întreg care returnează int -> int, adică o funcție de între, iar funcția f are tipul int * int -> int: e definită pe produsul cartezian int * int (perechi de întregi) cu valori întregi.

Potrivirea de tipare

În OCaml, potrivirea de tipare e un mecanism foarte expresiv; ne ajută să scriem cod concis, iar compilatorul verifică dacă am tratat toate cazurile. Prin potrivirea de tipare identificăm valori cu o anumită structură (liste, perechi, etc.), și dăm nume elementelor componente, pe care le folosim pentru a calcula valori (rezultatul dorit). Sintaxa generală e
match expresie0 with
  | tipar1 -> rezultat1
  | tipar2 -> rezultat2
...
Aceasta e o expresie care, în funcție de valoarea lui expresie0 are unul din rezultatele indicate; acestea trebuie să fie de același tip. Una din cele mai frecvente erori este de a încerca să dăm rezultate de tipuri diferite. Compilatorul va raporta eroarea: This expression has type tip2 but an expression was expected of type tip1
Încercarea tiparelor se face în ordine; se va ajunge la tiparul 2 doar dacă tiparul 1 nu s-a potrivit, etc. Deci, dacă avem tipare cu valori comune, ordinea scrierii lor contează!
Bara | indică începutul fiecărui tipar; primul | e opțional.
Putem avea și mai multe tipare cu același rezultat: | tipar1 | ... | tiparN -> rezultat .

Ilustrăm potrivirea prin tipare în câteva funcții simple (cum toate calculele în ML se fac în funcții). Cea mai simplă potrivire de tipare se face pe constante:

let not b = match b with
  | false -> true
  | true -> false
Am definit astfel negația booleană, o funcție bool -> bool . Dealtfel, not este un operator predefinit în ML.
Mai sus, singura folosire a argumentului b a fost pentru potrivirea de tipare: match b with . Mai departe, numele b nu a fost folosit. Același tipar se poate scrie mai simplu:
let not = function
  | false -> true
  | true -> false
Cuvântul cheie function definește o funcție, dar argumentul ei nu este numit (spre deosebire de fun x -> ... ), ci e identificat prin tiparele care urmează. Citim: not e o funcție care dacă ia argumentul false returnează true, etc.
Folosim deci function pentru funcțiile la care facem potrivire de tipare pe ultimul argument (pe care nu îl mai indicăm în lista de argumente, altfel funcția va avea un argument în plus!).

Fiecare parte din tiparul potrivit primește (e legată de) un nume care poate fi folosit ulterior în expresia rezultatului.
Potrivirea de tipare se face după structura valorii (constantă, sau compusă din elemente: pereche, listă, etc.); nu în funcție de o relație pe acea valoare. Nu putem scrie deci
let sign = function
| n > 0 -> 1

dar putem scrie

let sign = function
  | 0 -> 0   (* tipar cu valoare constanta *)
  | n -> if n > 0 then 1 else -1
Fiecare nume poate aparărea o singură dată în tipar: nu putem identifica o listă cu primele două elemente egale cu:
| h :: h :: t -> ...

Putem însă folosi condiția when:
| e1 :: e2 :: t when e1 = e2 -> rezultat1
| e1 :: e2 :: t -> rezultat2
Cum tiparele se evaluează în ordine, al doilea tipar, deși nu are nicio condiție suplimentară, se va potrivi doar pentru elemente diferite (adică atunci când nu s-a potrivit tiparul 1).

Desigur se poate folosi și testul în expresia rezultat:

| e1 :: e2 :: t -> if e1 = e2 then ... else ...
Putem indica prin tipare și structura parametrilor unei funcții, ca în exemplul următor, unde tiparul spune că argumentul funcției e o pereche. Funcția returnează o pereche cu elementele în ordine inversă (interschimbate):
let swap (x, y) = (y, x)

Marius Minea
Last modified: Mon Oct 14 23:10:00 EEST 2013