Limbajul de programare Curry

Curry
Curry
Paradigmăfuncțional, logic, non-strict, modular
Proiectat deMichael Hanus, Sergio Antoy, et al.
Tiparestatic, inferență
Implementări majorePAKCS (with Prolog as the target), mcc (with C as the target), KiCS2 (with Haskell as the target)
Influențat deHaskell
Sistem de operareportable
Prezență onlineCurry

Curry[1] este un limbaj de programare logică funcțională experimentală[2] bazat pe limbajul Haskell. Acesta îmbină elementele de programare funcțională și logică, inclusiv integrarea programării constrângerilor.

Este aproape un superset al lui Haskell, lipsit de sprijin, mai ales pentru supraîncărcare folosind clase de tip, pe care unele implementări le furnizează oricum ca o extensie a limbajului, cum ar fi compilatorul curcubeului Münster[3].

Baze de programare logică funcțională

Noțiuni de bază

Un program funcțional este un set de funcții definite prin ecuații sau reguli. Un calcul funcțional constă în înlocuirea subexprimărilor cu subexprimări egale (în ceea ce privește definițiile funcțiilor) până când nu mai sunt posibile înlocuiri (sau reduceri) și se obține o valoare sau o formă normală. De exemplu, luați în considerare funcția dublă definită de

double x = x+x

Expresia “double 1” se înlocuiește cu 1+1. Acesta din urmă poate fi înlocuit cu 2 dacă interpretăm operatorul “+” care trebuie definit printr-un set infinit de ecuații, de exemplu, 1+1 = 2, 1+2 = 3 etc. În mod similar, se pot evalua expresiile imbricate (unde se citează subexpresiile care trebuie înlocuite):

'double (1+2)' → '(1+2)'+(1+2) → 3+'(1+2)' → '3+3' → 6

Există și un alt ordin de evaluare dacă înlocuim argumentele operatorilor de la dreapta la stânga:

'double (1+2)' → (1+2)+'(1+2)' → '(1+2)'+3 → '3+3' → 6

În acest caz, ambele derivări conduc la același rezultat, o proprietate cunoscută drept confluență. Aceasta rezultă dintr-o proprietate fundamentală a limbilor funcționale pure, denumită transparență referențială: valoarea unui rezultat calculat nu depinde de ordinea sau timpul de evaluare, datorită absenței efectelor secundare. Acest lucru simplifică raționamentul și menținerea programelor funcționale pure.

Cât de multe limbi funcționale ca Haskell do, Curry acceptă definirea tipurilor de date algebrice prin enumerarea constructorilor lor. De exemplu, tipul de valori booleene constă în constructorii True și False care sunt declarați după cum urmează:

 data Bool = True | False

Funcțiile pentru booleani pot fi definite prin potrivirea tiparelor, adică prin furnizarea mai multor ecuații pentru valorile diferite ale argumentului:

 not True = False
 not False = True

Principiul înlocuirii egal cu egal este încă valabil, cu condiția ca argumentele actuale să aibă forma cerută, de exemplu:

not '(not False)' → 'not True' → False

Structurile de date mai complexe pot fi obținute prin tipuri de date recursive. De exemplu, o listă de elemente, unde tipul de elemente este arbitrar (denotat de variabila de tip a), este fie lista goală “[]”, fie lista ne-goală “x:xs” constând dintr-un prim element x și o listă xs:

 data List a = [] | a : List a

Tipul "List a" este de obicei scris ca [a] și listele finite x1:x2:...:xn:[] sunt scrise ca [x1,x2,...,xn]. Putem defini operațiuni pe tipuri recursive prin definiții inductive în cazul în care modelul de potrivire susține separarea convenabilă a diferitelor cazuri. De exemplu, operația de concatenare "++" pe listele polimorfe poate fi definită după cum urmează (declarația de tip opțională din prima linie specifică faptul că "++" are două liste ca intrare și produce o listă de ieșire, același tip nespecificat):

 (++) :: [a] -> [a] -> [a] 
 [] ++ ys = ys 
 (x:xs) ++ ys = x : xs++ys

Dincolo de aplicarea sa pentru diverse sarcini de programare, operația "++" este de asemenea utilă pentru a specifica comportamentul altor funcții în liste. De exemplu, comportamentul unei funcții care are ultimul element dintr-o listă poate fi specificat după cum urmează: pentru toate listele last xs = e dacă ∃ys:ys++[e] = xs.

Pe baza acestei specificații, se poate defini o funcție care să satisfacă această specificație utilizând funcții de programare logică. În mod similar cu limbile logice, limbile logice funcționale oferă căutarea unor soluții pentru variabilele cuantificate existențial. Spre deosebire de limbile logice pure, ele sprijină rezolvarea ecuațiilor peste expresiile funcționale imbricate, astfel încât o ecuație precum ys++[e] = [1,2,3] este rezolvată prin instanțierea ys în listă [1,2] și e la valoarea 3. In Curry one can define the operation last as follows:

 last xs | ys++[e] =:= xs = e where ys,e free

Aici, simbolul "=: =" este folosit pentru constrângerile ecuaționale pentru a oferi o distincție sintactică de la ecuațiile definitorii. În mod similar, variabilele suplimentare (adică variabilele care nu apar în partea stângă a ecuației definitorii) sunt explicit declarate de "unde ... free" pentru a oferi unele posibilități de a detecta bug-uri cauzate de tipărire. O ecuație condiționată a formulei l | c = r este aplicabil pentru reducere dacă condiția lui c a fost rezolvată. Spre deosebire de limbile pur funcționale în care condițiile sunt evaluate doar la o valoare booleană, limbile logice funcționale sprijină rezolvarea condițiilor prin ghicirea valorilor pentru necunoscute în condiție. În scopul rezolvării unor astfel de condiții se utilizează o îngustare așa cum este discutată în secțiunea următoare.

Îngustarea

Îmbinarea este un mecanism prin care o variabilă este legată de o valoare selectată dintre alternativele impuse de constrângeri. Fiecare valoare posibilă este încercată într-o anumită ordine, iar restul programului invocat în fiecare caz pentru a determina validitatea legării. Înlăturarea este o extensie a programării logice, prin faptul că efectuează o căutare similară, dar poate genera de fapt valori ca parte a căutării, mai degrabă decât să se limiteze doar la testarea acestora.

Îmbinarea este utilă deoarece permite unei funcții să fie tratată ca o relație: valoarea sa poate fi calculată "în ambele direcții". Exemplele din Curry din secțiunea anterioară ilustrează acest lucru.

După cum sa menționat în secțiunea anterioară, îngustarea poate fi considerată ca o reducere pe un grafic de termeni al programului și există adesea multe căi (strategii) diferite pentru a reduce un anumit graf de termeni. Antoy et al.[4] a demonstrat în anii 1990 că o strategie specială de îngustare, este optimă în sensul de a face o serie de reduceri pentru a ajunge la o "formă normală" corespunzătoare unei soluții care este minimă între strategiile solide și complete.

Modele funcționale

Regula care definește last arătat mai sus exprimă faptul că argumentul propriu-zis trebuie să se potrivească cu rezultatul micșorării expresiei ys++[e]. Curry poate exprima această proprietate și în următorul mod mai concis:

 last (ys++[e]) = e

Haskell nu permite o astfel de declarație, deoarece modelul din partea stângă conține o funcție definită (++). Un astfel de model este numit și model funcțional[5]. Modelele funcționale sunt activate de trăsăturile funcționale și logice combinate ale Curry și oferă suport pentru definirea concisă a sarcinilor care necesită potrivirea modelului profund în structurile de date ierarhice.

Non-determinism

Din moment ce Curry este capabil să rezolve ecuațiile care conțin apeluri funcționale cu valori necunoscute, mecanismul de execuție se bazează pe calcule nedeterministe, la fel ca la programarea logică. Acest mecanism sprijină și definirea operațiilor non-deterministe, adică operații care oferă mai mult de un rezultat pentru o intrare dată. Arhetipul operațiilor non-deterministe este operația infixată predefinită, ? . Numită operator de selecție, care returnează unul dintre argumentele sale. Acest operator este definit de următoarele reguli:

x ? y = x
x ? y = y

Astfel, evaluarea expresiei 0 ? 1 returnează 0, precum și 1. Calculul cu operații non-deterministe și calculul cu variabile libere prin îngustare are aceeași putere expresivă[6].

Regulile care definesc ? arată o caracteristică importantă a Curry: toate regulile sunt încercate pentru a evalua o anumită operațiune. Prin urmare, se poate defini prin

 insert x ys     = x : ys
 insert x (y:ys) = y : insert x ys

o operație de introducere a unui element într-o listă într-o poziție indeterminată, astfel încât perm de funcționare definit de

 perm []     = []
 perm (x:xs) = insert x (perm xs)

returnează orice permutare a unei anumite liste de intrări.

Strategii

Datorită absenței efectelor secundare, un program logic funcțional poate fi executat cu strategii diferite. Pentru a evalua expresiile, Curry folosește o variantă a strategiei de îngustare necesară, care combină evaluarea leneșă cu strategiile de căutare nedeterminate. Spre deosebire de Prolog, care utilizează backtracking pentru a căuta soluții, Curry nu stabilește o strategie specială de căutare. Prin urmare, există implementări ale lui Curry, cum ar fi KiCS2, unde utilizatorul poate selecta cu ușurință o strategie de căutare, cum ar fi căutare în adâncime (backtracking), căutare în lățime, căutare aprofundată sau căutare paralelă.

Note

  1. ^ Michael Hanus (ed.). „Curry: A Truly Integrated Functional Logic Language”. 
  2. ^ Sergio Antoy and Michael Hanus (). „Functional Logic Programming”. Communications of the ACM. ACM. 53 (4): 74–85. doi:10.1145/1721654.1721675. 
  3. ^ The Münster Curry Compiler
  4. ^ Sergio Antoy, Rachid Echahed and Michael Hanus (). „A Needed Narrowing Strategy”. Journal of the ACM. ACM. 47 (4): 776–822. doi:10.1145/347476.347484. ISSN 0004-5411. 
  5. ^ Antoy Sergio, Hanus Michael (). „Declarative Programming with Function Patterns”. Lecture Notes in Computer Science: 6–22. doi:10.1007/11680093_2. 
  6. ^ Antoy Sergio, Hanus Michael (). „Overlapping Rules and Logic Variables in Functional Logic Programs”. Lecture Notes in Computer Science: 87–101. doi:10.1007/11799573_9. 

Legături externe