A LISP programozási nyelv

Utasítások, vezérlési szerkezetek

Függvények kezelése

Egy kifejezés (lista vagy atom) feldolgozását az értelmező három lépésben végzi:

  1. a kifejezés beolvasása;
  2. a kifejezés kiértékelése;
  3. a kifejezés értékének kiírása.

Ha egy kifejezést azért írunk fel, hogy az értelmező kiértékelje, akkor a kifejezést formának is nevezzük. Ha a beírt forma lista, akkor az értelmezőprogram ennek az első elemét egy függvény nevének tekinti, a lista további elemeit pedig — ha vannak — a függvény argumentumainak. A forma kiértékelésekor az értelmezőprogram a függvényt a legtöbb függvény esetében az argumentumok értékére alkalmazza. Először tehát kiértékeli az argumentumként megadott kifejezéseket, majd az argumentumok értékére alkalmazza függvényt. A forma értéke, azaz a kiértékelés eredménye a függvénynek az argumentumok értékének helyén felvett értéke lesz.

Például a (PLUS 5 6) függvénykifejezés értéke 11 lesz.

Későbbi funkcionális nyelvekben megjelent az ún. lusta kiértékelés. Ez azt jelenti, hogy csak akkor értékelődik ki egy kifejezés, ha szükség van rá, így egy változó értékének nem kötelező definiáltnak lenni. Ilyen a Lispben nincs!

Vannak beépített aritmetikai és listakezelő függvények.

Fontosak azok a függvények, amelyek egy szimbólumhoz hozzárendelnek egy értéket. Ilyen például a SETQ.

(SETQ szimb.atom kifejezés)

Itt a szimbolikus atomhoz, a kifejezés értékét rendeli. Ha azt akarjuk, hogy egy kiértékelés eredménye maga a kifejezés (pl. atom) legyen, akkor rakjunk elé egy aposztrófot.

A SETQ második argumentuma (csak a második argumentuma) kiértékelődik, és ez az érték lesz a függvénykifejezés értéke. Például a (SETQ ALFA (PLUS 3 6)) után ALFA értéke 9 lesz.

Az egyargumentumos QUOTE fgv. megtiltja az argumentumának kiértékelését, így lehet elérni, hogy ne értékelődjön ki a második argumentum ("jobboldal"). Például tegyük fel, a POCKOK-SZÁMA változónak a 6-ot adtunk értékül. Ekkor (SETQ A POCKOK-SZÁMA) esetén A-nak 6, míg (SETQ A (QUOTE POCKOK-SZÁMA)) után pedig a POCKOK-SZÁMA atom lesz az értéke. Hivatkozhatunk szimbólum értékének az értékére is az EVAL függvény segítségével.

Saját függvényt a DEFUN függvény segítségével definiálhatunk. Ennek szintaxisa:

(DEFUN fvnév (paramlista) (törzs))

A függvény neve nem tartalmazhatja az alábbi karaktereket: kettőspont, pontosvessző, nyitó- és csukó zárójel valamint idézőjel.

A paraméterlista esetén megengedett az üres paraméterlista is. A paraméterlistában megengedettek a következő kulcsszavak is: optional, rest, key. Az optional kulcsszó után felsorolt paraméterek opcionálisak. Nem kötelező megadni. Ha valamelyik opcionális paraméter nincs megadva akkor az NIL-el helyettesítődik be. A rest kulcsszó esetén az összes további paraméter egy listába fog bekerülni, majd ezt a listát a függvény kiértékelése során feldolgozhatjuk. A key kulcsszóval az egyes paraméterekre a kulcsaik alapján is tudunk hivatkozni. Példák:

(defun foo (a b &optional c) (list a b c))
(defun foo (&key a b c) (list a b c))
(defun foo (&rest a b c) (list a b c))

Szekvencia

(PROGN lista1 lista2 ...)

Ez sorra kiértékeli a listákat, majd az utolsó kiértékelés eredményével tér vissza.

(LET ((változó1 initkif1) (változó2 initkif2) ...) kif1 kif2 ...) (LET* ((változó1 initkif1) (változó2 initkif2) ...) kif1 kif2 ...)

Egy új scope-ot hoz létre, amiben az első listában szereplő változó-neveket a megfelelő inicializációs kifejezésekhez köti. Ebben az új scope-ban értékeli ki a törzsében szereplő kifejezéseket; a PROGN-hez hasonlóan a LET-kifejezés értéke az utolsó kifejezés értéke.

LET esetén az inicializáció párhuzamosan történik abban az értelemben, hogy az inicializációs kifejezések még nem látják az újonnan kötött változókat; LET* esetén az inicializáció szekvenciálisan történik. A következő példa segít ennek megértésében:

; Ennek a kifejezésnek az értéke 11 (LET ((x 10)) (LET ((x 20) (y x)) (+ y 1))) ; Ezé viszont 21 (LET ((x 10)) (LET* ((x 20) (y x)) (+ y 1)))

Elágazás

(IF (feltétel) (igaz ág utasítása) (hamis ág utasítása))

Van CASE is:

(CASE (kif) (atom1 utasítás1) (atom2 utasítás2) ... (OTHERWISE utasítás))

Az összehasonlítás EQ szerint történik. Ha egyik atommal sem egyezik meg a kifejezés értéke, és nincs OTHERWISE ág, akkor nil értéket kapunk. A CASE makró egy másik változata az ECASE, amelyben nem adhatunk meg OTHERWISE ágat, és ha nincs illeszkedő ág, akkor TYPE-ERROR szignált kapunk.

Feltételes kifejezés

Egy feltételes kifejezést a COND függvény segítségével írhatunk le. A függvény általános alakja:

(COND (feltétel1 tevékenység1) (feltétel2 tevékenység2) ... (feltételN tevékenységN))

A feltételes kifejezés kiértékelése:

A feltételes kifejezéseknél használhatjuk a when és unless függvényeket is.
A when szintaxisa.

(when (feltétel utasítás1 utasítás2 ... utasításn)
When esetén ha a feltétel igaz volt akkor a when után szereplő összes utasítás végrehajtódik.
Az unless szintaxisa.
(unless (feltétel utasítás1 utasítás2 ... utasításn)
unless esetén ha a feltétel hamis volt akkor az unless után szereplő összes utasítás végrehajtódik.

Ciklus

Az iteráció a nemfunkcionális programozási nyelvek egyik legjellemzőbb programozási eszköze. Segítségével a program egy részét többször is végre lehet hajtani. A LISP-ben azt nevezzük iterációnak, ha egy formát egymás után többször kiértékelünk, és egy meghatározott feltétel teljesülésétől függ, hogy meddig kell a kiértékelést ismételnünk.
A funkcionális nyelvek alapvetően a rekurzív függvényeket szeretik, de hatékonysági okokból (tár és idő) az imperatív nyelvekre jellemző iterációs "utasítások" is bekerültek.

Számlálásos ciklus, azaz az RPT makró

A legegyszerűbb iteratív makró az RPT. Ennek segítségével azt írhatjuk elő, hogy egy S-kifejezés hányszor értékelődjön ki. (Ez a függvény az imperatív nyelvek szóhasználata szerint a számlálásos ciklusnak felel meg.)
A makró alkalmazásakor a második argumentum értéke annyiszor ismétlődik meg, amennyi az első argumentum értéke (az első argumentum egy szám, a második egy tetszőleges forma). A kifejezés értéke a második argumentum utoljára kapott értéke lesz.

Elöltesztelő ciklus: a WHILE makró

A WHILE makró segítségével olyan ciklusokat szervezhetünk, amelyekben a kifejezés mindaddig kiértékelődik, amíg egy feltétel teljesül. A WHILE akárhány argumentumú, de legalább két argumentumot meg kell adni. Az első argumentum tetszőleges S-kifejezés — ez az iteráció ún. megállási feltétele —, amely mindig kiértékelődik. Ha értéke nem NIL, kiértékelődnek a további argumentumok is, majd ismét az első argumentum értékelődik ki és így tovább. Ez mindaddig ismétlődik, amíg az első argumentum értéke NIL nem lesz, ekkor a kiértékelés befejeződik. A WHILE függvény értéke mindig NIL.

Az UNTIL makró

Az UNTIL makró éppen a WHILE fordítottja. Argumentumait ugyanúgy kell megadnunk, mint a WHILE esetében, de a második és a további argumentumok csak akkor értékelődnek ki, ha az első argumentum — a megállási feltétel — értéke NIL. Ez ismétlődik mindaddig, ameddig az első argumentum értéke először bizonyul igaznak. Az UNTIL értéke ez az érték lesz.

A DO makró

Az újabb LISP rendszerekben megtalálható a DO makró is, melynek segítségével bonyolultabb ciklusokat építhetünk fel. A DO makróval mindazt meg lehet valósítani, amit az RPT, a WHILE vagy az UNTIL függvénnyel felírhatunk. Ugyanúgy, mint az UNTIL esetében, a DO alkalmazásakor is S-kifejezések értékelődnek ki újra és újra, egészen addig, amíg a feltételként megadott kifejezés értéken igaz nem lesz. A DO makró fontos lehetősége, hogy megadhatunk egy változólistát argumentumként. A változókhoz a változólistán értéket is rendelhetünk. A DO általános alakja:

(DO ((változó_1 érték_1) (változó_2 érték_2) ... (változó_m érték_m)) (feltétel tevékenység) S-kifejezés_1 S-kifejezés_2 ... S-kifejezés_n)

Az első argumentum a változólista, amely kételemű allistákból áll. Az allisták első eleme tetszőleges szimbólum lehet, ezeket DO-változóknak nevezzük, a második elem pedig tetszőleges S-kifejezés lehet. A DO kiértékelésének kezdetekor minden változónak az értéke a hozzá tartozó S-kifejezés értéke lesz. A legtöbb LISP változatban a változólistán egyelemű allista is szerepelhet, ekkor a megfelelő DO-változó értéke kezdetben NIL.

A DO kiértékelése után a DO-változók elveszítik lokális értéküket, és ha volt előzőleg értékük, azt visszakapják.

A DO második argumentuma egy kételemű lista, amelynek elemei tetszőleges S-kifejezések lehetnek. A DO harmadik és esetleges további argumentumai tetszőleges S-kifejezések lehetnek, ezek alkotják a DO törzsét. Ha a megállási feltétel értéke NIL, akkor a törzset alkotó S-kifejezések egymás után kiértékelődnek. Az utolsó S-kifejezés után azonban nem fejeződik be a DO kiértékelése, hanem újra kiértékelődik a feltétel - és azt követően minden S-kifejezés - egészen addig, amíg a feltétel értéke igaz érték nem lesz. Ekkor értékelődik ki a megállási feltétel után álló tevékenység, és ennek értéke lesz a DO kifejezés értéke.

A LOOP makró

Az újabb lisp verziókban megtalálható a loop makró is. Ezzel széles lehetőségünk nyílik a különböző formátumú ciklusok használtára.
Végtelen ciklus esetén nem adunk meg kilépési feltételt. Például: (loop (print 'ciklus))
Lehetőségünk van egy loop ciklust egy tartományom belül is lefuttatni. (loop for x from 1 to 3 do (print x)). Itt a to helyett használhatjuk a downto kulcsszót is ha változó értékét csökkenteni szeretnénk.
A below valamint above kulcsszavakkal is megadhatjuk, hogy meddig tartson az iteráció. (loop for x above 1 from 4 do (print x))
Ezen felül gyűjteményeket is bejárhatunk a loop makró segítségével. (loop for (item) on '(1 2 3) do (print item))
Az for-as-across segítségével járhatunk be array típusú adatszerkezeteket(pl a string is ilyen). Ilyenkor addig rakja be a változóba az elemeket amíg van elem az array-ben.

A PROG speciális forma

A DO makró esetén a törzs kiértékelése után, hacsak a megállási feltétel igaz nem lett, mindig végrehajtódik az iterációs lépés. Ez azzal a megkötéssel jár, hogy a törzsben szereplő kifejezések mindig ugyanabban a sorrendben értékelődnek ki. A PROG speciális forma ezzel szemben lehetővé teszi, hogy a kifejezéseket tetszőleges sorrendben értékeljük ki. A PROG-kifejezések a következő alakúak:

(PROG (változó_1 változó_2 ... változó_m) S-kifejezés_1 S-kifejezés_2 ... S-kifejezés_n)

A kifejezés első eleme a PROG szimbólum, ezután következik a PROG változóinak listája. A PROG-változóknak kezdetben NIL az értéke. A változólista után tetszőleges S-kifejezés következik (címke, GO álfüggvény, RETURN álfüggvény). Ha a PROG törzsének utolsó S-kifejezése is kiértékelődött, és ez nem tartalmazott olyan GO-kifejezést. melynek hatására a kiértékelés a törzs valamely más helyén folytatódik, akkor a PROG kiértékelése befejeződik. A PROG kiértékelése akkor is befejeződik, ha a törzsben egy RETURN kifejezés kiértékelődik. Ennek alakja: (RETURN S-kifejezés).

Lokális függvények

(Common) Lisp-ben nem csak lokális változókat tudunk létrehozni, hanem lokális függvényeket a FLET vagy a LABELS segítségével. Hasonlóan a LET-hez a lokális függvények is csak az adott környezetben láthatóak. Ez főképp abban az esetben hasznos, amikor egy bonyolultabb lokális függvényre van szükségünk, amit könnyebb így definiálni mint LAMBDA kifejezésként. Az egyszerűsített leírásuk igen hasonló:
(flet (function-definition*) body-form*)

valamint
(labels (function-definition*) body-form*)

ahol a function-definition a következőképp néz ki:
(name (parameter*) form*)

A különbség a FLET és a LABELS között, hogy a FLET által létrehozott függvények nevei csak a FLET törzsében használhatóak, míg a LABELS esetén, már a függvények törzsében is. Lényegében a LABELS-el lehet rekurzív függvényeket definiálni(hiszen már a függvények törzsében is használhatjuk őket), míg FLET-tel nem. Mivel a függvények be vannak ágyazva ezért használhatják egy esetleges külső LET változóit, így kevés valódi bemeneti változóra van szükségük.

Számlálásos ciklus, azaz az RPT makró

Lehetőségünk van (Common) Lisp-ben lokális makrókat definiálni. Ezeket a MACROLET segtségével tudjuk megtenni. Hasonlóképp definiálható mint a DEFMACRO, csak nem globális környezetbe lesz definiálva hanem lokálisan.