Lisp II: Úvod do jazyka

23.07.08

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 jako 1 + 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

Komentáře

  1. 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… :-)

  2. 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.

  3. 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:

    ; funkce vrací druhou mocninu
    (defun nadruhou (a)
     (* a a))
    
    ; funkce fun bere jako argument funkci
    ; kterou aplikuje na číslo pět
    (defun fun (funkce)
     (funcall funkce 5))

    Když teď chceme zavolat funkci fun a předat ji jako argument funkci nadruhou, 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 hodnotu nadruhou. A to funcall je 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 to funcall, 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? :-)

Pošli komentář

Komentáře jsou moderované. Každý komentář musí před publikováním autor Laboratoře schválit. Komentáře, které jistě nebudou zveřejněny: vulgární, vyvolávající flame, netýkající se tématu, komentáře psané bez diakritiky (háčky, čárky).

Komentáře jsou uzavřeny