Lisp

programovací jazyk

Lisp (historicky LISP) je rodina multiparadigmatických programovacích jazyků s dlouhou historií. Jeho název je zkratka pro List processing (zpracování seznamů). Přestože se jedná spíše o akademický jazyk, používá se i na reálných projektech, např. v oboru umělé inteligence. Používá ho také například textový editor Emacs či konstrukční program AutoCAD.

Lisp byl původně specifikován v roce 1958. V současné době se jedná o druhý nejstarší vysokoúrovňový jazyk, který se stále ještě používá v praxi; starší už je pouze Fortran. Lisp byl původně navržen jako programovací jazyk pro matematické výpočty a byl silně ovlivněn syntaxí Lambda kalkulu. Rychle se stal favorizovaným programovacím jazykem ve světě umělé inteligence. Lisp se stal průkopníkem v mnoha programových technikách, například: stromové struktury, automatická správa paměti nebo dynamické typování. Lisp nevnímá rozdíl mezi kódem a daty, díky čemuž má jednoduchou syntaxi. Celý program je tak složen z s-výrazů nebo ozávorkovaných seznamů ve tvaru (f a b c), kde na prvním místě je operátor/funkce a na dalších argumenty funkce. Všechny další funkce jazyka mají identickou syntaxi.

Z Lispu jsou odvozeny i další jazyky – například Tcl, Smalltalk nebo Scheme. Tvůrcem jazyka byl John McCarthy.

Syntaxe

editovat

Základním zápisem v Lispu je seznam. Zapisuje se tímto způsobem:

(1 2 "ahoj" 13.2)

Tento seznam obsahuje čtyři prvky:

  • celé číslo 1
  • celé číslo 2
  • řetězec znaků „ahoj“
  • reálné číslo 13,2

Seznam v příkladu reprezentuje uspořádanou čtveřici. Závorky v jazyce Lisp nefungují tak jako v matematice, ale pouze označují začátek a konec seznamu.

Seznamy jsou v Lispu implementovány jako binární strom degenerovaný na jednosměrný spojový seznam.

Co se seznamem Lisp udělá, záleží na okolnostech.

Příkazy

editovat

Příkazy jazyka Lisp se zapisují také jako seznam, jehož první prvek seznamu je název příkazu.

Například sčítání je realizováno příkazem +. Odpovídající konstrukce v jazyce vypadá takto:

(+ 1 2 3)

Interpret odpoví 6.

Ukázka kódu

editovat

Program hello world lze zapsat několika způsoby. Nejjednodušší vypadá takto:

(format t "Hello, World!")

Funkce se v Lispu definují klíčovým slovem defun:

(defun hello ()
  (format t "~&Hello, World!~%"))
(hello)

Na prvních dvou řádcích je definice funkce hello, na třetím řádku je tato funkce svým jménem zavolána.

Funkcím lze předávat i argumenty. V následujícím příkladu je ukázka funkce fact, která vypočítá faktoriál zadaného čísla:

(defun fact (n)
  (if (zerop n)
    1
    (* n (fact (- n 1)))))

Pro výpočet faktoriálu čísla 6 předáme tuto hodnotu jako argument funkci fact:

(fact 6)

Návratovou hodnotou funkce bude hodnota 720.

Lisp má jako jeden z mála jazyků propracovaný systém maker, díky kterým lze velmi výrazným způsobem ovlivnit celý jazyk. Makra jsou nejprve načtena v READ části REPLu, následně je provedena makroexpanze (tu provádí preprocesor) a až poté je celý výraz vyhodnocen běžnou EVAL částí. Nemá smysl uvažovat o aplikaci makra, v době vyhodnocení výrazu již žádné makro neexistuje. Makro pouze přepisuje text/kód předtím, než se předhodí k vlastnímu vyhodnocení. Zásadní rozdíl mezi makrem a funkcí pak je, že makro nevyhodnocuje své argumenty při zavolání funkce.

Quote, Unquote, Quasiquote

editovat

Abychom mohli makra vůbec používat, musíme mít nějaké nástroje k transformaci kódu. Běžně se používá speciální operátor quote, který vrátí následný výraz tak jak mu ho předáme — žádnou část nevyhodnotí. Jako syntaktickou zkratku můžeme použít apostrof '.

;; Mohlo by se zdát, že quote není potřebný operátor, když máme list,
;; ale jak je vidět, je mezi nimi zásadní rozdíl — funkce list vyhodnocuje
;; všechny své argumenty, quote nevyhodnotí nic.
> (quote (1 2 3))
(1 2 3)

> (list 1 2 3)
(1 2 3)

> (quote (1 (+ 2 3 4) 5))
(1 (+ 2 3 4) 5)

> (list 1 (+ 2 3 4) 5)
(1 9 5)

> (quote (a b c d))
(A B C D)

> (list a b c d)
Error: The variable A is unbound.

> '(1 2 3)
(1 2 3)

Abychom mohli i kvotované části nechat něco vyhodnotit, musíme mít mechanismus, kterým zrušíme ono kvotování a vrátíme se zpět k vyhodnocování. K tomu slouží speciální operátory unquote a quasiquote. Quasiquote se chová stejně jako quote, pouze s tím rozdílem, že ve svém těle umožňuje použít unquote, který vyhodnotí daný výraz. Syntaktická zkratka pro unquote je čárka , a pro quasiquote zpětný apostrof `.

> `(1 2 ,(+ 3 4))
(1 2 7)

> `(list 1 2 ,(list 3 4))
(LIST 1 2 (3 4))

> `('a 'b ,(list (+ 1 2) (+ 3 4)) c d)
((QUOTE A) (QUOTE B) (3 7) C D)

Základní práce s makry

editovat

Makra se vytvářejí pomocí speciálního operátoru defmacro. Nejjednodušší příklad může být definice vlastní podmínky, vlastního ifu. Pomocí makra by to vypadalo následovně:

(defmacro my-if (cond true false)
  `(if ,cond
       ,true
     ,false))

Makro se chová stejně jako běžný if:

> (my-if 1 2 3)
2

;; Makro vrátí dvě jedničky, protože jednou se vytiskne
;; a jednou se vrátí jako výsledek funkce print.
> (my-if 1 (print 1) (print 2))
1
1

> (my-if nil (print 1) (print 2))
2
2

Při definici „vlastního“ ifu musíme použít makro, protože nevyhodnocuje své argumenty. Kdybychom nadefinovali if jako funkci, nechovalo by se to stejně, protože argumenty už by se vyhodnotily při volání funkce a tím pádem by se vždy vyhodnotily obě větve podmínky.

(defun my-bad-if (cond true false)
  (if cond
      true
    false))

;; Příklady volání:

;; Zde proběhne vyhodnocení správně
> (my-bad-if 1 2 3)
2

;; Při tomto volání se chybně vytiskne jedna šestka
> (my-bad-if 1 (print 5) (print 6))
5
6
5

Problémy spojené s makry

editovat

Při používání maker si musíme dávat pozor na dva klasické problémy – dvojí vyhodnocení a symbol capture. Představme si if, který v true větvi automaticky vrátí výsledek podmínky a ve false větvi vrátí předaný argument. Ukázka, jak by to mělo fungovat:

; 1 je true, tak vrátí 1
> (if-false 1 2)
1

; Výsledný seznam je true, vrátí seznam
> (if-false (member 2 '(1 2 3 4 5)) 'nic)
(2 3 4 5)

; nil je false, vrátí symbol nic
> (if-false (member nil '(1 2 3 4 5)) 'nic)
NIC

Naivní implementace by mohla vypadat takto:

(defmacro if-false-1 (cond false)
  `(if ,cond
       ,cond
     ,false))

Toto makro zdánlivě funguje. Ovšem do doby, než na něj pustíme kód s vedlejším efektem:

;; Funguje jak má
> (if-false-1 (member 2 '(1 2 3 4 5)) 'nic)
(2 3 4 5)

;; Funguje jak má
> (if-false-1 (member nil '(1 2 3 4 5)) 'nic)
NIC

;; Makro incf zvyšuje hodnotu symbolu o jedna.
;; Nefunguje jak má — očekáváme, že volání vrátí dvojku.
> (let ((a 1))
    (if-false-1 (incf a) 'nic))
3

Kód (incf a) se v těle makra vyhodnotil dvakrát, proto nám to vrátí trojku. Kód po makroexpanzi vypadá takto:

(LET ((A 1))
  (IF (INCF A) 
      (INCF A) 
    'NIC))

Řešením je navázat vyhodnocenou podmínku na nějaký symbol:

(defmacro if-false-2 (cond false)
  `(let ((cond-help ,cond))
     (if cond-help
         cond-help
       ,false)))

Teď už se výraz vyhodnotí pouze jednou:

> (let ((a 1))
    (if-false-2 (incf a) 'nic))
2

Externí odkazy

editovat