|
EszközökMás nyelveken
|
LispA Lisp programozási nyelv (helyesebben nyelvcsalád) hosszú történetre tekint vissza. Eredetileg rekurzív függvények absztrakt ábrázolására tervezték, de hamarosan a mesterséges intelligencia kutatás előszeretettel alkalmazott nyelvévé vált, amikor a 70-es, 80-as években ezen terület a virágkorát élte. Ma a Lisp nyelveket számos területen alkalmazzák[1], és közkedvelt a számításelmélet oktatásában is. A Lisp név az angol „List Processing” (listafeldolgozás) kifejezésre vezethető vissza (maga a lisp szó angolul pöszét, pöszítést jelent.) A Lisp nyelvek fő adatstruktúrája ugyanis a láncolt lista. Az alapvető listakezelő műveletek az összes nyelvjárásban megegyeznek. Emellett közös sajátosság a futásidejű típusosság, a funkcionális programozásra jellemző jegyek, és az, hogy a programkód adatként manipulálható. Tetszőleges Lisp nyelven írt program azonnal felismerhető a jellegzetes szintaktikájáról. A programkód ugyanis egymásba ágyazott listák, azaz zárójelezett, ún. S-kifejezések (S-expression, sexp) sorozata. Ennek a primitív szintaktikának köszönhetően a Lisp nyelveken írt programokhoz nagyon egyszerű elemzőt (parser-t) és kódot generáló (meta-) programokat írni, ugyanakkor emberi szemmel könnyű elveszni a nyitó- és csukózárójelek erdejében. A könnyű elemezhetőség volt az egyik oka a nyelv népszerűségének a 70-es években, amikor még nem volt elengendő számítási kapacitás összetett, többmenetes fordító- és értelmezőprogramok futtatásához. Formális specifikációjának első, 1958-ban készített változatával a Lisp a második legöregebb magas szintű programozási nyelv (a Fortran után). A megalkotása óta elmúlt közel 50 évben a nyelv sokat változott, és számos nyelvjárással gazdagodott. A ma legelterjedtebb változatai az általános célú Common Lisp és Scheme nyelvek.
[szerkesztés] TörténetAz Information Processing Language (Információfeldolgozó nyelv) volt az első mesterséges intelligencia nyelv, amelyet 1955-56-ban alkottak meg, és amelyben már számos, a Lispben alapvető koncepció felbukkant, úgy mint a láncolt listák és a rekurzió. A Lisp nyelvet John McCarthy alkotta meg 1958-ban, mialatt az MIT-n dolgozott. Eredményeit 1960-ban publikálta („Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I.”). Cikkében rámutatott, hogy pár egyszerű operátor és egy függvények ábrázolására alkalmas primitív szintaktika segítségével teljes értékű, általános programozási nyelv alkotható. A matematikában ez a gondolat Alonzo Church újfajta matematikai logikai rendszere, az 1930-as és 40-es években kidolgozott lambda-kalkulus révén már korábban ismert volt. Az első Lisp értelmezőt Steve Russel készítette egy IBM 704 típusú számítógépre, amelynek két alacsony szintű utasítása a nyelv két legfontosabb, listák felbontására használt függvénye lett: A Lisp kifejező erejének és rugalmasságának köszönhetően gyorsan népszerűvé vált a mesterséges intelligencia kutatói közösség körében. Számos előnye mellett a Lispnek természetesen megvannak az árnyoldalai: a programok jelentős mennyiségű, részeredményként szolgáló adatot gyártanak, amelyek foglalják a memóriát. Az ilyen rövid időre lefoglalt memóriaterületeket rendszeresen fel kell szabadítani egy ún. hulladékgyűjtő algoritmus (garbage collector) segítségével. Ez komoly problémát jelentett a 70-es évek memóriában és számítási kapacitásban szűkölködő számítógépeinek. Az egyre növekvő felhasználói tábor igényeinek és kormányzati támogatásnak köszönhetően végül megszületett a Lispgép, azaz a célzottan Lisp programok futtatására készített számítógép. Mára a hulladékgyűjtő algoritmusok, a fordító- és értelmezőprogramok fejlődésének, a memória- és számítási kapacitás robbanásszerű növekedésének következtében a Lispgépekhez hasonló dedikált megoldások feleslegessé váltak. Az 1980-as és 90-es években komoly energiát fordítottak az egyre jobban eltávolodó nyelvjárások egyesítésére, ennek eredménye a Common Lisp, amely magába foglalta a kiváltani kívánt dialektusok összes lényeges tulajdonságát. 1994-ben az Amerikai Szabványügyi Hivatal, az ANSI nyilvánosságra hozta a nyelvjárást specifikáló szabványt (ANSI X3.226-1994). Ekkorra azonban a Lisp használata a 70-es évek virágkorához képest már jelentősen visszaszorult. A számítógépprogramozásban ma alapvető és teljesen általános esetszétválasztásos (ha … akkor …, egyébként …) vezérlési szerkezetet McCarthy vezette be a Lispben, amelyet aztán az Algol átvett és népszerűsített. [szerkesztés] SzintaxisA Lisp kifejezésorientált nyelv. Ez annyit tesz, hogy a legtöbb nyelvvel ellentétben a Lispben nincs különbség „kifejezések” és „parancsok” között, minden adatot és utasítást kifejezések formájában írunk le. Amikor az értelmező kiértékel egy kifejezést, eredményként egy értéket (vagy értékek listáját) kapunk, amely aztán beágyazható újabb kifejezésekbe, vagy akár kifejezésként maga is kiértékelhető. McCarthy 1958-as cikke két kifejezéstípust különböztetett meg: Az S-kifejezéseket (S-expression, sexp), vagyis szimbolikus kifejezéseket, és az M-kifejezéseket, vagyis meta kifejezéseket, amelyek S-kifejezéseket alakítanak át újabb S-kifejezésekké. Ez utóbbi típus azonban nem talált támogatókra, és a legtöbb mai Lisp változat S-kifejezéseket használ mind az adatok, mind a kód manipulálásához. Sokan kritizálták a zárójelek túltengését az S-kifejezésekben (ezt tükrözi a Lisp mozaikszó ironikus kifejtése, a „Lots of Irritating Superfluous Parentheses”, azaz „rengeteg irritáló és felesleges zárójel” is), de az igazság az, hogy az egyszerű, rendkívül szabályos szintaktika adja a Lisp legfőbb erejét: a lehetőséget, hogy a kódot adatként kezeljük. Az, hogy mindent kifejezésekkel írunk le, nagy rugalmasságot biztosít a nyelvnek. Mivel a Lispben magukat a függvényeket is listákként definiáljuk, akként is tudjuk kezelni őket, és nem okoz technikai nehézséget programot generáló programok írása (ezt hívjuk metaprogramozásnak). Számos Lisp nyelvjárás ki is aknázza ezt a tulajdonságot, és lehetővé teszi makrók készítését, amelyek tkp. függvényeket generáló, paraméterezhető függvények. A Lisp-ben egy listát zárójelekkel határolunk, az elemeket szóközzel választjuk el egymástól: (1 2 "izé") Ezen lista elemei az A kifejezéseket szintén lista alakban írjuk, mégpedig prefix jelölést használva. A lista legelső eleme egy művelet (angolul form), azaz egy függvény, operátor, makró stb. neve. A lista további elemei adják a művelet argumentumait. A (list 1 2 "izé") kifejezés az (list 1 2 (list 3 4)) kifejezés értéke A számtani műveletek hasonlóan működnek (itt szokatlan lehet a prefix jelölés): (+ 1 2 3 4) értéke 10. Bizonyos rendhagyó műveletek (special form-ok) vezérlési szerkezetként szolgálnak. Az (if nil (list 1 2 "izé") (list 3 4 "bigyó")) eredménye Egy másik rendhagyó művelet, a (defun miez (a b) (list 1 a 3 b)) függvénydefiníció után a [szerkesztés] Párok és listákA Lisp nyelvben a listák egyszeresen láncolt listák. A lista celláit cons celláknak (sokszor egyszerűen pároknak) nevezzük. Egy cons cella két mutatóból áll, amelyeket car-nak és cdr-nek hívunk. Ezen elnevezések eredete az első Lisp megvalósításig nyúlik vissza, az akkori IBM számítógépeken ugyanis így nevezték a Lisp értelmező által használt hivatkozásfeloldó műveleteket (amelyek elérték a mutatók átal megcímzett memóriaterületet). A cons cellákból tetszőleges adatszerkezet építhető, a leggyakoribb azonban a szabályos lista, amelynek car mutatója a lista első elemére, cdr mutatója pedig a farkára, tehát kivétel nélkül vagy a Mint látjuk, a listák nem atomi értékek, hanem cons cellák láncolt sorozatai. Egy listára hivatkozó változó nem más, mint a lista első cons cellájára hivatkozó mutató. Egy lista bejárása a Egy zárójelezett S-kifejezés egy ilyen láncolt listát definiál. Ugyanazon lista ábrázolására S-kifejezésként számos lehetőség kínálkozik. Egy cons cellát felírhatunk az ún. pontozott pár jelöléssel A párok sokrétű felhasználhatóságának köszönhetően elterjedt, de téves vélekedés, hogy a Lisp-ben nincs is más adatstruktúra. Valójában azonban a legtöbb Lisp megvalósítás tartalmaz más struktúrákat is, mint pl. a vektorok (vagy tömbök), hash táblák stb. [szerkesztés] Megosztott struktúraA Lisp listák, lévén hogy egyszerű láncolt listák, tartalmazhatnak közös, megosztott szakaszokat. Nevezetesen, két (vagy több) listának lehet ugyanaz a farka. Például az alábbi kód hatására (setq ize (list 1 2 3)) (setq bigyo (cons 0 (cdr ize))) az A tisztán funkcionális programozás hívei éppen ezért elkerülik a destruktív függvények használatát. A Scheme-ben, amely a Lisp funkcionális jellegére nagy hangsúlyt fektet, a desktruktív függvények neve egy-egy figyelemfelkeltő felkiáltójelben végződik, mint pl. [szerkesztés] Önazonos kifejezések és az idézésAmikor egy kifejezést kiértékelünk, egy másik, többnyire egyszerűbb kifejezést, értéket kapunk. A A Lisp-ben lehetőségünk van arra is, hogy egy értéket megvédjünk a kiértékeléstől. Erre szolgál az idézés, azaz a (setq ize ´ize) Ezután akármilyen mélységben értékeljük ki A makrókat lehetővé tevő nyelvjárások ennél bonyolultabb idézőjeleket is definiálnak, mint pl. a részleges idézést (backquote vagy quasiquote, jele a hátravessző, Az önazonos és az idézett kifejezések alkotják a Lispben a konstansokat. (Bár ez az elnevezés nem pontos, mert a gyakorlatban lehetőség van ezek értékének megváltoztatására is, amely komoly félreértésekre és zavarokra adhat okot. Elképzelhető például, hogy az [szerkesztés] A programkód listaszerkezeteAz eddigi példákban szereplő karaktersorozatok szigorúan véve nem Lisp programok, csak azok írott reprezentációi. Amikor beírjuk őket a Lisp értelmezőbe, az elemző (a Lisp esetében a Egyszerű Lisp megvalósításokban az értelmező közvetlenül ezt a belső formát dolgozza fel a program futtatásához, azonban a legtöbb létező implementáció a hatékonyság érdekében futtatás előtt egy alacsonyabb szintű byte-kódra (adott esetben gépi kódra) fordítja a függvényeket. [szerkesztés] Kiértékelés és a OKK (REPL)-ciklusA Lisp rendszereket gyakran egy interaktív parancssoron keresztül vezéreljük, amelyet adott esetben kiegészíthet egy integrált fejlesztői környezet (IDE). A felhasználó vagy közvetlenül a parancssorba írja be a kifejezéseket, vagy utasítja a környezetet, hogy tegye meg ezt helyette (mondjuk egy állományból). A Lisp értelmező először beolvassa az adott kifejezést, ezután kiértékeli azt, végül kiírja az eredményt. Ennek megfelelően a Lisp parancssori végrehajtást gyakran "olvasás-kiértékelés-kiírás" vagyis OKK-ciklusnak, angolul "read-eval-print-loop"-nak, REPL-nek hívjuk. A ciklus három ütemének egy-egy alapvető Lisp függvényt lehet megfeleltetni. A Az A Ezen három függvény megléte esetén egy primitív OKK-ciklus írása nagyon egyszerű: (loop (print (eval (read)))) ahol a [szerkesztés] PéldaprogramokAlább olvasható pár jellegzetes Lisp példaprogram. Mivel a rekurzió, amely a Lisp nyelvnek jellegzetes vonása, számos matematikai definíció alapja is, a szintaktika elsajátítása után az ilyen definíciók lefordítása Lisp-re nem okoz különösebb nehézséget. A példák ilyen definíciók Lisp-beli megfelelői. Az első függvény a jól ismert faktoriális függvény egy lehetséges megvalósítása:
(defun factorial (n)
(if (<= n 1)
1
(* n (factorial (- n 1)))))
Álljon itt egy másik változat, amely a legtöbb Lisp megvalósításban hatékonyabb, mert jobbrekurzív, azaz a függvény törzsének, legutolsó, legkülső hívása a rekurzív hívás, és ez lehetőséget ad optimalizálásra:
(defun factorial (n &optional (acc 1))
(if (<= n 1)
acc
(factorial (- n 1) (* acc n))))
Vegyük észre, hogy itt a részeredmény egy külön argumentumban (az akkumulátorban) gyűlik. Ellenpontként következzék egy iteratív változat a Common Lisp ciklusszervező
(defun factorial (n)
(loop for i from 1 to n
for fac = 1 then (* fac i)
finally return fac))
Az alábbi függvény egy listát fordít meg (a Lisp-ben valójában van egy azonos nevű, azonos funkcionalitású beépített függvény):
(defun reverse (l &optional acc)
(if (atom l)
acc
(reverse (cdr l) (cons (car l) acc))))
[szerkesztés] MegvalósításokA Lisp nyelveknek számos megvalósítása készült. A legkorábbiak értelmezőprogramok voltak, habár a Lisp programok gépi kódra fordításra is hamar terjedni kezdett. Az 1980-as években néhány cég ún Lispgépeket kezdett gyártani és forgalmazni – ezek a számítógépek eleve Lisp programok futtatására voltak tervezve és optimalizálva. A modern Common Lisp rendszerek túlnyomó többsége gépi kódra fordítja a programokat futtatás előtt, a legtöbb Scheme megvalósítás ugyanakkor ma is értelmezőprogram alapú. Lisp megvalósítások alapjául szolgálhat a SECD virtuális gép is. [szerkesztés] Nyelvjárások és változatokKözel ötven éves története során a Lisp-nek számos nyelvjárása jött létre. Mindegyikben közös az S-kifejezések szerepe és szerkezete. Ezen túlmenően a legtöbb nyelvjárásnak több megvalósítása is készült, a népszerű CommonLisp-nek például több mint egy tucat implementációja ismert. Az egyes nyelvjárások között komoly eltérések lehetnek. A Scheme és a Common Lisp például még az olyan alapvető kérdésekben is eltér, mint a függvényeket definiáló művelet neve. Ugyanazon dialektus különböző megvalósításai azonos szintaktikát követnek és ugyanazokat a beépített függvényeket definiálják, a könyvtári függvények készlete azonban már eltér. (Figyelem: az alábbi lista vegyesen tartalmaz különféle nyelvjárásokat és megvalósításokat, távolról sem teljes, és nem tükröz fontossági, népszerűségi vagy időrendi sorrendet!)
[szerkesztés] Lásd még
[szerkesztés] Külső hivatkozások
|