Lisp je jiný.

To je první věc, kterou si musíte uvědomit, pokud chcete programovat v Lispu.
Systém vyhodnocování
Lisp pracuje se symbolickými výrazy. Nejjednodušší symbolický výraz
Lispu je atom. Atom může být buď číslo, nebo symbol. Pomocí symbolů a
čísel můžeme poté skládat složitější výrazy, například seznamy.
Seznam se skládá z dalších symbolických výrazů, takže může klidně
obsahovat další seznamy. Pro tuto chvíli nám bude stačit vědět, že
seznam se skládá ze symbolických výrazů a je ohraničem závorkami.
Příklad seznamu: (1 2 a (b c)). Toto je čtyřprvkový seznam
(obsahuje dvě čísla, jeden symobl a jeden dvouprvkový seznam).
Čísla se vyhodnocují sama na sebe. Tedy když do posluchače napíšete
číslo, Lisp vám vrátí totéž číslo. Zkuste si to. Docela exoticky
působí možnost zapisovat zlomky přímo ve tvaru zlomku. Pokud chcete napsat
jednu polovinu, napíšete zkrátka 1/2. Systém to pochopí jako
zlomek a bude s ním tak pracovat. Například pokud k tomuto zlomku
přičtete jedničku, Lisp vám nevrátí 1,5, ale vrátí vám skutečně
3/2.
Symboly představují proměnné, jak je známe z ostatních jazyků. Lisp
nerozlišuje symboly pro funkce a symboly pro proměnné, takže symbol
karkulka může být stejně tak „proměnná“ jako funkce.
S vyhodnocováním symbolů je už to krapet složitější, protože jejich
vyhodnocení závisí na tom, kde zrovna ten symbol použijeme. Obecně mohou
nastat dva případy: na symbol není navázaná žádná hodnota, potom Lisp
při pokusu o vyhodnocení vrátí chybu. V opačném případě se snaží
symbol vyhodnotit na jeho vazbu.
CL-USER 23 > a
Error: The variable A is unbound.
Proměnnou a jsme zkrátka nenavázali, tak ji nemůžeme vyhodnocovat. Něco
jiného je, když ji navážeme na nějakou hodnotu. To můžeme udělat nehezky
pomocí makra setf:
CL-USER 24 : 1 > (setf a 10)
10
CL-USER 25 : 1 > a
10
Vidíme, že po navázání čísla deset na symbol a a
následném vyhodnocení symbolu a již Lisp neháže chybu. Teď
ale nastává drobný problém, protože v Lispu má každý symbol dva
chlívečky, kam můžete něco uložit. Jeden chlíveček je „hodnotový“ a
druhý „funkční“. Hodnotový slouží k ukládání hodnot, zato
funkční pro ukládání funkcí. Před chvílí jsme uložili desítku do
hodnotového chlívečku, proto můžete vzápětí provést toto:
CL-USER 26 : 1 > (defun a()5)
A
Tímto jsme do funkční části uložili primitivní funkci, která pouze
vrátí číslo pět. Symbol a zavoláme prostým napsáním
a, funkci a zavoláme takto: (a).
CL-USER 27 : 1 > a
10
CL-USER 28 : 1 > (a)
5
K čemu to je dobré? Je to primárně proto, abyste si mohli pojmenovat
třeba argumenty funkcí jak chcete. V Lispu existuje vestavěná funkce
list, kterou nelze přepsat. Pokud by nebyly k dispozici dva
chlívečky, již byste symbol list nemohli použít jinak než
jako funkci. Takhle si můžete klidně zvolit list jako název
argumentu nějaké funkce. Systém, kdy se veme funkce a kdy hodnota, je trochu
složitější a budeme se jím zabývat později.
Prefixová notace
První věc, která vás zaskočí, je, že Lisp používá prefixovou
notaci, namísto klasické infixové. Co to znamená? Že veškeré
operátory se umisťují před samotné operandy. Například součet trojky a
sedmičky byste v klasickém jazyce zapsali jako 3 + 7. V Lispu
by to bylo + 3 7, přesněji (+ 3 7). V Lispu
pracujeme se seznamy a pokud chceme vyhodnotit složitější výraz, musí být
v seznamu. Prefixová notace má mnoho výhod:
- Jednodušeji se píše parser, protože ten hned na začátku ví, co se s čím bude dělat. Stejně tak to vždy víte vy. Stačí se kouknout na první prvek seznamu a přesně víte, jaká operace bude probíhat.
- Díky prefixové notaci musíte také správně uzávorkovávat, protože
jinak by vznikl neplatný výraz: výraz
(+ 3 * 5 2)se pravděpodobně nevyhodnotí tak, jak byste očekávali; musíte ho zapsat správně do seznamů:(+ 3 (* 5 2)). - Jednotnost jak u funkcí, tak u operátorů. Stejně jako je na začátku seznamu operátor, je vždy na začátku seznamu i funkce.
- Možnost použití více operandů zároveň. Konkrétně u sčítání
můžete sčítat více čísel zároveň, ne pouze dvě:
(+ 1 2 3 4). V jiném jazyce by se tohle zapsalo jako1 + 2 + 3 + 4.
Nevýhoda prefixového zápisu je zřejmá – je to nezvyk a
v počátcích to může programátora docela mást. Obzvláště blbě
vypadají porovnávací operátory: (< 5 10). V běžném
jazyku by se napsalo čitelnější 5 < 10.
Ještě se trochu blíže podíváme na základní operátory. Použití by
již mělo být vcelku jasné. Například (- 10 5) odečte pětku
od desítky (ekvivalentní matematický zápis: 10 - 5). Stejně
jako plus, i minus bere více argumentů: (- 15 10 2 3) ⇒
vrátí nulu (ekvivalentní matematický zápis: 15 - 10 - 2 - 3).
Podobně u násobení a dělení:
CL-USER 2 > (* 10 3)
30
CL-USER 3 > (* 3 6 7)
126
CL-USER 4 > (/ 80 5)
16
CL-USER 5 > (/ 80 5 4)
4
Kombinace více operátorů by měla být také zřejmá. Zkrátka místo
čísla vložíme seznam s dalším výrazem. (3 + 5) * (9 - 1) *
2 by se zapsalo: (* (+ 3 5) (- 9 1) 2).
Pozor na to, že operátory plus a krát mohou být použity bez argumentu. Lisp poté vrátí nulový prvek, u sčítání nulu a u násobení jedničku. U dělení a odečítání je vždy třeba alespoň jeden argument. V případě, že je právě jeden, vrací Lisp opačnou hodnotu.
CL-USER 13 > (+)
0
CL-USER 14 > (*)
1
CL-USER 15 > (/ 5)
1/5
CL-USER 16 > (- 5)
-5
Na prvním místě seznamu musí být vždy nějaký operátor nebo funkce. Pokud není, Lisp vyhodí chybu, protože neví, co má s takovým seznamem dělat:
CL-USER 29 : 1 > (1 2 3)
Error: Illegal argument in functor position: 1 in (1 2 3).
Zpět k vyhodnocování výrazů
Teď se již můžeme vrátit k vyhodnocování výrazů. Říkali jsme, že v každém symbolu může být uložena funkce a hodnota. Z výše řečeného poměrně jasně plyne, jak se bude Lisp chovat a kdy který chlív zvolí. Pokud se bude symbol vyskytovat na prvním místě seznamu, zvolí funkční chlív, pokud se bude vyskytovat jinde, zvolí se hodnota. Ukázkový příklad:
CL-USER 30 : 2 > (setf fun 10)
10
CL-USER 31 : 2 > (defun fun (cislo)
(* cislo 2))
FUN
CL-USER 32 : 2 > (fun fun)
20
Nejprve na symbol fun navážeme hodnotu deset. Potom na symbol
fun navážeme funkci, která vrátí dvojnásobek předaného
čísla. A následně zavoláme (fun fun). První fun
se vyhodnotí jako funkce (je na prvním místě seznamu) a druhý
fun se vyhodnotí jako desítka (není na prvním místě).
To by mohlo pro dnešek stačit, příště se podíváme blíže na funkce.
Další díly seriálu
- Lisp I: Funkcionální programování a Lispworks
- Lisp II: Úvod do jazyka (právě čtete)
- Lisp III: Funkce, vyhodnocování funkcí
Petr Staníček · 23. červenec 2008, 09:34 · #
Teda, docela dlouho mi trvalo, než mi došlo, že ve výrazech jako „CL-USER 32 : 1 > a“ je to „CL-USER 32 : 1 >“ jen prompt a ne součást výrazu… :-)
David Grudl · 23. červenec 2008, 11:29 · #
Je mezi stejně pojmenovanou funkcí a „proměnnou“ nějaký skutečný vztah, nebo jde jen o souvislost v mysli programátora? Jako třeba v jiných programovacích jazycích.
Timy · 23. červenec 2008, 12:53 · #
@Petr: :-) Když tak dám do článku upozornění, aby to nemátlo i někoho dalšího.
@David: Co myslíš tím pojmem vztah? Můžeš si třeba ručně vynutit, aby Lisp vzal funkční hodnotu namísto té standardní. Třeba když chceš předat funkci jako argument jiné funkce. A vzhledem k tomu, že se ta předaná funkce uloží do argumentu zase jako hodnota, musíš si ještě ručně vynutit spuštění funkce. Hmm, trochu složité :-). Příklad:
Když teď chceme zavolat funkci
funa předat ji jako argument funkcinadruhou, uděláme to takhle:(fun #'nadruhou)Pomocí toho
#'vytáhneme z výrazu funkci, ne hodnotu. Kdybychom to neudělali, předali bychom argumentu nenavázanou hodnotunadruhou. A tofuncallje tam proto, že v samotné funkci se hodnoty argumentů uchovávají jako hodnoty, ne jako funkce. Sice jsme předali funkci, ale uložili jsme ji jako hodnotu, takže musíme ještě ručně vynutit její spuštění. Kdyby tam nebylo tofuncall, Lisp by se snažil hrábnout do funkčního chlívu, kde nic není.Jinak když se člověk koukne do inspektoru, může vidět něco takového. Výraz „A“ má v hodnotě desítku a ve funkci nějakou funkci.
Je to ten vztah, který jsi myslel? :-)