
Lisp -- mocný programovací jazyk
Lisp je jedním z nejzajímavějších programovacích jazyků. Za dlouhá léta jeho
existence se kolem něj vytvořilo mnoho mýtů, řada špičkových softwarových
vývojářů na něj nedá dopustit, mnozí jiní jej naopak stejně vášnivě zatracují.
Velmi často se ovšem za různými emocemi skrývá především neznalost. Pokusme se
tedy trochu nakouknout na některé zajímavé rysy Lispu.
Trocha historie
Lisp vznikl koncem padesátých let dvacátého století jako po FORTRANu druhý
nejstarší prakticky používaný programovací jazyk vyšší úrovně. Podobně jako
jeho předchůdce přežil dlouhá desetiletí až dodnes. Jazyk původně určený pro
výzkum v oblasti umělé inteligence za tu dobu prošel bohatým vývojem a stal se
univerzálním programovacím nástrojem. Nicméně si uchoval své základní principy
a stejně jako FORTRAN za svou neustálou popularitu vděčí z velké části právě
jim. Zatímco FORTRAN se vyznačuje jednoduchostí nevyžadující praktickou žádnou
nadbytečnou režii a umožňující tak za cenu nízké úrovně jazyka generovat velmi
efektivní kód, Lisp se naopak vyznačuje jiným druhem jednoduchosti, dovolujícím
vysokou úroveň programové abstrakce.
Pojem Lisp dnes může být chápan poměrně široce. Během
dlouhého historického vývoje vznikla celá řada verzí Lispu i víceméně zcela
nových programovacích jazyků na Lispu založených. Co se týče Lispu samotného,
časem vznikla potřeba "společné řeči" a implementátoři různých verzí jazyka se
postupně dohodli na nové, společné verzi nazvané Common Lisp,
která byla v roce 1984 definována v podobě ANSI standardu. Toto pořadí vývoje standardu se ukázalo
být velkou výhodou. Dnes je velmi častá praxe, kdy je definice programovacího
jazyka určována de facto standardem v podobě jeho nejpoužívanější vedoucí
implementace. Ta se vyvíjí cestou pokusů a omylů, přinášejících
nekompatibility mezi jednotlivými verzemi jazyka a zátěž v podobě
neopustitelných historických omylů. Naproti tomu standard Common Lispu vznikl
na základě předchozích zkušeností, velmi promyšleně a díky tomu platí bez
jediné změny dodnes.
Některé mutace Lispu přesto pokračovaly svojí vlastní cestou, za všechny můžeme
uvést velmi známý Emacs Lisp, který je rozšiřujícím jazykem populárního
i nenáviděného editoru Emacs, z velké části napsaného právě v Emacs Lispu.
Z odlišných programovacích jazyků na Lispu založených, které se vesměs snažily
napravit skutečné i domnělé nedostatky Lispu, jsou dnes asi nejznámějšími Scheme a
Dylan.
Kód jako data
Původní autoři Lispu projevili obrovskou předvídavost v podobě poměrně
jednoduché myšlenky, že není žádný zásadní rozdíl mezi programem a daty. Tato
základní myšlenka Lispu přežila dodnes a je dosud jedním z nejpodstatnějších a
kupodivu i poměrně unikátních rysů Lispu. V čem spočívá?
Lisp v principu používá dva stavební prvky svých programů a dat:
symboly a
seznamy symbolů. Seznamy symbolů mohou reprezentovat stejně
tak data jako program, záleží čistě jen na tom, jakým způsobem je použijeme.
Podívejme se na jednoduchý příklad:
(:= x (+ x 1))
Takovým zápisem může být reprezentován tříprvkový seznam, jehož prvky jsou
postupně symboly := a x a seznam (+ x 1), opět složený ze tří prvků, a to
symbolů +, x a 1. Seznam můžeme celkem přirozeně chápat jako obyčejnou
datovou strukturu, ale stejně tak, v závislosti na funkcích asociovaných se
symboly := a +, například jako zvýšení číselné hodnoty proměnné nazvané x
o jedničku.
Není tedy rozdílu mezi programovým kódem a jeho přirozenou reprezentací dat.
Mnohé populární programovací jazyky se vyznačují zcela odlišnou reprezentací
programového kódu a dat nebo dokonce ani nemají vlastní přirozenou reprezentaci
dat a vypomáhají si proto velmi těžkopádnou berlou v podobě XML. Lispoví
programátoři si nad tím mohou ukroutit hlavu, protože data reprezentují pro ně
tím nejjednodušším a nejprostším způsobem -- lispovým kódem. Mohou je pak
kdykoliv velmi jednoduše načíst do paměti lispovým parserem. Důsledkem tohoto
principu je pak reflexivita kódu, tj. jeho schopnost
transformovat sama sebe do libovolné jiné podoby. K ukázkám uplatnění této
vymoženosti se dostaneme za chvíli.
Před dalším výkladem poznamenejme, že uvedené příklady vesměs nejsou korektním
zápisem v nějaké konkrétní mutaci Lispu, nechceme čtenáře obtěžovat z hlediska
výkladu nepodstatnými podrobnostmi. Koho zajímá konkrétní syntaxe a sémantika
některé z verzí Lispu, nalezne odpovídající odkazy na konci článku.
Prefixová notace
Na programátora navyklého pouze práci s dnes populárními procedurálními jazyky
jako jsou C/C++, Java, Python nebo Perl, který poprvé spatří typický lispový
program, obvykle učiní hluboký dojem dva aspekty jeho zápisu: prefixová notace
a zdánlivá přemíra závorek. Výše uvedený příklad by totiž ve "svém" jazyce
zapsal nejspíše nějak jako
x := x + 1 ;
a prefixově-závorková notace se mu zdá být nepřehledná a obtížně zvládnutelná.
Opustí-li však zaběhaná schémata matematických zápisů, od dětství nám
vštěpovaná, uvědomí si některé nezanedbatelné výhody lispového zápisu:
- Zápis je konzistentní. V konvenčních jazycích se velmi často volání
většiny funkcí zapisuje analogicky jako v Lispu, tj. prefixově a se
závorkami, například
set_variable (x, x + 1) ;
Ty jsou však doplněny množinou speciálních, mnohdy pevně daných, funkcí,
tzv. operátorů, pro které platí zcela jiná pravidla zápisu. Obvykle se jedná
o odlišnou lexikální podobu jejich identifikátorů kombinovanou s vypuštěním
závorek a infixovou notací.
Lisp nerozlišuje mezi normálními funkcemi a operátory, vše se zapisuje stejně
a nevzniká proto potřeba definovat nejroztodivnější komplikované formy zápisu
podpořené hojným používáním různých druhů závorek a zaváděním
všemožných značek (v tomto směru je velmi typický a kontrastní jazyk
Perl) a zápis programu tedy zůstává přehledný a snadno srozumitelný.
- Okamžitým důsledkem výše uvedeného je, že Lisp klade jen minimální omezení na
znaky použitelné v identifikátorech (symbolech). Programátorovi nic nebrání
pojmenovat si nějakou funkci prime? nebo si nazvat proměnnou
50%-of-the-sum. Samozřejmostí je pak používání přehlednějšího a na většině
klávesnic snáze zapsatelného znaku - místo oblíbeného podtržítka.
- Lisp nezná problémy s prioritou operátorů. V prefixové notaci (stejně jako
notaci postfixové, viz jazyky jako Forth nebo Postscript) je pořadí vyhodnocení všeho dáno
zcela jednoznačně.
- Normální prefixový zápis operátorů se závorkami umožňuje zbytečně se
neopakovat. Chceme-li konjunkci, zapíšeme její operátor pouze jednou:
(and PODMÍNKA-1 PODMÍNKA-2 ...)
V jazyce definujícím konjunkci jako infixový operátor jsme nuceni jej
opakovat:
PODMÍNKA-1 and PODMÍNKA-2 and ...
Případ, kdy je počet operandů podobného "binárního" operátoru menší než dvě,
nelze infixově zapsat vůbec, zatímco v prefixovém zápisu žádná potíž
nevzniká.
Vše uvedené samozřejmě snižuje nejen požadavky na některé mentální procesy
programátora, ale zjednodušuje i práci překladači, jeho lexikální a syntaktické
části.
Rozšiřitelnost jazyka
Žádný programovací jazyk se neobejde bez určité množiny primitivních operací.
V Lispu jich také několik je (je jich však výrazně méně, než je vůbec klíčových
slov v jazyce C++!), nazývají se special forms. Některé
programovací jazyky touto množinou končí, Lisp jí naopak začíná. Princip
vzájemné záměny programu a dat je totiž ve spojení s onou nepočetnou množinou
primitivních operací natolik mocný, že umožňuje bez zásahu do jazyka definovat
prakticky jakékoliv nové konstrukce.
Jako příklad si můžeme uvést operátor IF, vracející hodnotu jedné ze dvou
programových větví, rozlišených dle zadané
podmínky. Předpokládejme, že náš programovací jazyk tento operátor nemá, je
však turingovsky úplný a tudíž jej můžeme vyjádřit pomocí jiných operací.
Díky tomu, že v Lispu je program zároveň daty, nebrání nám nic
automaticky transformovat zápis
(if CONDITION THEN-PART ELSE-PART)
do jiné cílové podoby, řekněme
(:= stop false)
(while (and (= stop false) CONDITION)
(:= result THEN-PART)
(:= stop true))
(while (= stop false)
(:= result ELSE-PART)
(:= stop true))
(return result)
Stačí napsat lispový program naznačený převod programových dat provádějící a
asociovat jej s identifikátorem if. Jedná se o tzv. makro.
V jazycích, které schopností manipulace se svým kódem nedisponují, jako je
například Java nebo Python, je podobné zavedení nového operátoru (k lítosti
mnoha vývojářů) zpracovaného v době kompilace nemožné. Uvedenou operaci v nich
totiž nelze realizovat
funkcí, protože by vždy došlo k vyhodnocení obou alternativ
rozlišených podmínkou během jejich předávání příslušné funkci. Plnohodnotné
zavedení
takového operátoru je možné pouze transformací programu.
Lépe jsou na tom jazyky C nebo C++ disponující makroprocesorem, který výše
uvedenou definici umožňuje. Nejdou však dostatečně daleko -- zatímco v Lispu
je přepis kódu (dat) realizován v samotném Lispu a tudíž může provádět
libovolné vyčíslitelné operace, v C/C++ se jedná pouze o turingovsky
nekompletní převodník, ve kterém sofistikovanější operace nelze realizovat.
Jedná se navíc o pouhou textovou substituci, zatímco v Lispu se provádějí
skutečné manipulace s daty (kódem).
Lispová makra nejsou samozřejmě omezena jen na operátory. S jejich pomocí lze
definovat libovolné programové konstrukce, kupříkladu různé druhy cyklů,
definice a zpracování výjimek nebo klidně i systém objektově orientovaného
programování. S pomocí jednou definovaných maker můžeme mnohdy relativně
snadno budovat makra složitější, vedoucí k sofistikovaným systémům,
připomínajícím nový lispový jazyk napsaný v Lispu.
Mechanismus maker je velice mocný a přitom se vyskytuje jen v malém množství
programovacích jazyků. Jedním ze zřejmých důvodů je "syntaktické znečištění"
těchto jazyků komplikující zápis a analýzu kódu ve formě dat a nezahrnutí
principu
program = data. Když přišlo objektově orientované programování, museli
programátoři v jazyce C vymýšlet nové programovací jazyky. Programátoři
v Lispu naopak objektově orientované programování do svého jazyka pouze
implementovali, bez jakékoliv sebemenší změny jazyka.
Závorky
Makra naplňují význam názvu jazyka: Lisp = LISt Processing. Tento název bývá
často parodován, například "Lost In Stupid Parentheses" nebo "Lots of Silly
Irritating Parentheses". Vyjadřuje již zmíněný dojem, že lispové programy
oplývají přemírou závorek. Ona velice často zdůrazňovaná přemíra závorek je
však ve skutečnosti pouhým
mýtem. Stačí si totiž uvědomit, že:
- zápis
(foo x y) obsahuje stejné množství závorek jako foo (x, y), avšak
o čárku méně;
- použití infixových operátorů většinou znamená zavedení různých hvězdiček,
rovnítek, hranatých a složených závorek (!), čárek, středníků a dalších znaků
se speciálním významem, které jsou v Lispu vcelku nepotřebné;
- kdo chce v Lispu zapisovat aritmetické či jiné výrazy infixovou notací, může,
stačí si definovat příslušné makro převádějící infixový zápis na prefixový.
Funkcionální programování
Lisp má často pověst funkcionálního programovacího jazyka. Nikoliv zcela bez
příčiny, jisté funkcionální rysy má, kupříkladu že jakékoliv volání navenek
vypadá jako funkce (s případnými vedlejšími efekty) a vrací hodnotu. V Lispu
vrací hodnotu nejen matematické operace, ale i volání if (pro které tudíž
není potřeba definovat dvě formy, jednu jako příkaz, druhou jako operátor, jako
je tomu například v C) nebo while cyklus. Neexistuje v něm tedy rozdíl mezi
příkazy a výrazy známý z jiných programovacích jazyků.
Ve skutečnosti je však Common Lisp, stejně jako ostatní varianty Lispu a
většina příbuzných jazyků, ponejvíce univerzálním jazykem, který umožňuje
programovat procedurálně, funkcionálně, objektově nebo třeba logicky,
v závislosti na vašich potřebách a vkusu. Jediné, co po něm nemůžete chtít, je
to, aby vás v něčem vynuceně omezoval.
Potřebujete-li provádět automatickou logickou argumentaci o programu, asi sáhnete spíše po nějakém čistém funkcionálním nebo logickém
jazyce a pro zamezení možnosti nepovoleného provádění vedlejších efektů dáte nejspíše přednost jazyku kompilovaného do kódu typu Java Virtual Machine.
Na závěr
Rozsah článku nám neumožnil zmínit všechny zajímavé vlastnosti moderního Lispu,
například jeho mocný, ale přitom poměrně jednoduchý, objektový systém.
V případě čtenářského zájmu lze těmto tématům věnovat další články.
Pokud vás rysy jazyka Lisp zaujaly a chcete se o něm dozvědět více, naleznete
další informace například na následujících adresách:
|