Interpreter za parcijalno rekurzivne funkcije


Napomena:

Interpreter je napisan u Pythonu, a stavljen je na web uz pomoć JavaScripta i biblioteke Py-Script (koja omogućava izvršavanje Pythona u browseru). JavaScript ne podržava višedretvenost, pa je nemoguće istovremeno izvršavati kod u Pythonu i refreshati DOM-objekte. Drugim riječima, nije moguće prikazati rezultate svake naredbe čim ona završi, već se mora pričekati da se izvrše sve naredbe --- te će se tada svi rezultati ispisati u output. Ako želite pratiti tijek izvršavanja naredbi, možete otići na "More tools -> Developer tools" --- rezultati svake naredbe se tamo ispisuju u konzolu čim se ispravno interpretiraju.

Osnovne mogućnosti

Na početku rada, interpreter prepoznaje samo inicijalne funkcije.


/* višelinijski
komentar */
// jednolinijski komentar

Z(5) // 0
Sc(7) // 8
Sc(I(Z(6))) // 1 

            

Svaka naredba se piše u svom retku. Gornje naredbe predstavljaju pozive funkcija --- one će rezultirati ispisom rezultata u konzolu.

Kompliciranije funkcije možemo definirati na dva načina: primitivnom rekurzijom ili direktnom definicijom.

Primitivna rekurzija

Kod definicije bilo koje nove funkcije, potrebno je paziti da je interpreteru poznata čitava desna strana jednakosti. Drugim riječima, sve funkcije (ili relacije) koje se pojavljuju na desnoj strani neke jednakosti moraju biti definirane iznad (osim ako su inicijalne). Pogledajmo kako se definira zbrajanje i množenje (primitivnom rekurzijom) u skladu s ovim pravilom.


add(x,0) := x  // početni uvjet
add(x,Sc(y)) := Sc(add(x,y))  // korak

add(2,add(1,1))  // 4

mul(x,0) := 0
mul(x,Sc(y)) := add(x,mul(x,y))  // možemo koristiti funkciju add jer je definirana iznad

mul(add(2,3),mul(2,2))  // 20

            

Kao što vidimo, jednakost pišemo s := kako bismo naglasili da definiramo novu funkciju.

Direktna definicija

Nove funkcije možemo direktno definirati po točkama, bez korištenja primitivne rekurzije:


f(x,y) := add(mul(3,x), Sc(y))  // f(x,y) = 3x + (y + 1)
g(x) := f(x,0)  // g(x) = 3x + 1

f(2,3)  // 10
g(2)  // 7

        

Relacije

U čitavom interpreteru ne pravimo bitnu razliku između funkcija i relacija. Naime, relacije smatramo funkcijama s kodomenom \(\{0,1\}\). Oznaka za definiciju nove relacije je :<=> (ali zbog sličnosti s funkcijama može se pisati i :=). Pogledajmo kako se definira jednostavna relacija \(>\).


Positive(0) := 0
Positive(Sc(x)) := 1
    
Greater(x,y) :<=> Positive(sub(x,y))

Greater(2,3)  // 0
Greater(3,2)  // 1
Greater(2,2)  // 0

            

Prvo smo primitivnom rekurzijom definirali Positive --- karakterističnu funkciju skupa \(\mathbb{N}_+\). Zatim smo definirali relaciju Greater kompozicijom Positive i oduzimanja. Primijetimo da je \(\texttt{Greater(x,y)}\) uvijek 0 ili 1, za bilo koje \(x,y \in \mathbb{N}\).

Logički izrazi

U interpreteru možemo pisati i logičke izraze kako bismo definirali nove funkcije ili relacije. Podržane logičke operacije su konjunkcija &&, disjunkcija || i negacija !. Logički izraz se parsira po standardnim prioritetima operatora --- zagrade imaju najveći prioritet, a redom ih slijede negacija, konjunkcija i disjunkcija. Pogledajmo kako se definira relacija Equal koja je istinita ako su dva broja jednaka.


Equal(x,y):<=>!Greater(x,y) && !Greater(y,x)

Equal(2,3)  // 0
Equal(3,3)  // 1

            

Unutar logičkog izraza možemo staviti nešto čija vrijednost nije nužno element skupa \(\{0,1\}\) (npr. poziv neke funkcije). U tom slučaju pretpostavljamo da je 0 laž, a bilo koji drugi prirodan broj istina (odnosno jednak 1). To uvelike širi opseg izraza koji se mogu parsirati, pa su mogući i ovakvi izrazi:


Custom(x,y) :<=> !Greater(y,x) && Divides(y,x) || Equal(x,y)
GreaterAndRightAtLeastTwo(x,y) :<=> sub(x,y) && pd(y) && 42

Custom(10,2) // 1, jer je negacija od 2 > 10 istinita, i 2 dijeli 10
GreaterAndRightAtLeastTwo(3,2) // 1, jer 3 - 2 > 0, 2 - 1 > 0 i 42 > 0
GreaterAndRightAtLeastTwo(2,1) // 0, jer 2 - 1 > 0 i 42 > 0, ali 1 - 1 = 0

        

Listovi

Listovi u apstraktnom sintaksnom stablu predstavljaju osnovne gradivne jedinice programa koje samostalno imaju neko (semantičko) značenje. Kako bismo razumjeli ulogu listova, pogledajmo izraz \(f(L_1,L_2)\). Izraze \(L_1\) i \(L_2\) nazivamo listovima jer se očito nalaze na iznimno važnom mjestu u kodu --- argumenti su funkcije \(f\). Pogledajmo koje su sve vrste listova podržane (odnosno čemu sve smiju biti jednaki \(L_1\) i \(L_2\)):

Minimizacija

Funkciju možemo definirati minimizacijom uz pomoć ključne riječi \(\texttt{mu}\). Minimizacija može biti ograničena (operatorima \(<\) ili \(\leq\)) ili neograničena.

Pogledajmo nekoliko primjera:

SpecialSc(x) := (mu z) Greater(z,x)  // najmanji broj veći od x 
div(x,y) := pd((mu z <= x) Greater(mul(z,y),x))  // cjelobrojno dijeljenje

SpecialSc(5)  // 6
div(10,3)  // 3

            

Primijetimo da je, u prethodnom primjeru, čitav izraz \(\texttt{(mu z <= x) Greater(mul(z,y),x)}\) jedan list, iako se sastoji od više drugih listova. Ovo je u skladu s time da je list osnovna gradivna jedinica programa koja ima neko značenje. Primjerice, \(\texttt{(mu z <= x)}\) nije list, jer sama oznaka za minimizaciju nema značenje bez pripadne relacije --- ona je samo dio sintakse.

Brojeća funkcija

Sintaksa za brojeću funkciju je vrlo slična minimizaciji, ali sada zahtijevamo da postoji gornja granica do koje brojimo.


Prime(p) :<=> Equal((#d <= p) Divides(d,p),2)

Prime(5)  // 1
Prime(6)  // 0

        

Napomenimo da minimizacija i brojeća funkcija rade i bez zagrada, odnosno moguće je pisati \(\texttt{mu z <= x}\) umjesto \(\texttt{(mu z <= x)}\).

Grananje

Funkciju možemo definirati grananjem kao u sljedećem primjeru:


h(x,y) := if{Greater(x,y) : 0, Greater(y,x) : 1, add(x,y)}

h(3,2)  // 0
h(2,3)  // 1
h(2,2)  // 4

        

Uvjeti s lijeve strane znakova \(\texttt{:}\) se provjeravaju redom, a vrijednost izraza \(\texttt{if}\{\ldots\}\) je jednaka vrijednosti s desne strane prvog zadovoljenog uvjeta. Ukoliko niti jedan uvjet nije zadovoljen, vrijednost izraza je dana nakon zadnjeg zareza (\(\texttt{else}\) uvjet). U skladu s prijašnjim napomenama, uvjeti ne moraju biti relacije --- mogu biti bilo koji listovi.

Infiksni operatori

Kako bismo olakšali korištenje interpretera i izbjegli ugnježđivanje poziva funkcija unedogled, uvodimo infiksne operatore. Njih možemo definirati da se ponašaju kao bilo koja druga funkcija, a možemo ih definirati i primitivnom rekurzijom. Pišemo ih unutar uglatih zagrada, a operatori unutar njih mogu biti bilo koji nizovi specijalnih znakova.


[x - y] := sub(x,y)
[x ** y] := mul(x,y)  // operator se može sastojati od više znakova

[x + 0] := x
[x + Sc(y)] := Sc([x + y])

[5 - 2]  // 3
[2 ** 3]  // 6
[2 + 3]  // 5
[2 + [3 ** add(3,1)]]  // 14

    

Kompleksniji primjeri

Za kraj pokazujemo razne primjere koji kombiniraju sve što smo iznad pojasnili.


factorial(0) := 1
factorial(Sc(n)) := mul(Sc(n),factorial(n))

nextprime(p) := (mu q <= mul(p,2))(Prime(q) && Greater(q,p))

Zagrade(x,y,z) :<=> !(Greater(x,y) && Greater(y,z)) && (Greater(z,y) && Greater(y,x))

k(x) := (mu y) Z(y)  // parcijalno rekurzivna funkcija čije računanje nikad ne stane

Grananje(x,y) := if{k(x) : 4, [Z(x)+1]}

[[add(10,[2+3]) ** [0 - 2]] + [[7 ** nextprime(6)] - 12]]  // 37
Zagrade(5,6,7)  // 1
factorial(7)  // 5040, ali se dugo računa
Grananje(2,3)  // računanje nikad ne stane jer ovisi o funkciji k