Lisp: Porovnání verzí

Přidáno 5 063 bajtů ,  před 12 lety
→‎Ukázka kódu: Přidána kapitola o makrech. Ještě není dokončená.
m (typo)
(→‎Ukázka kódu: Přidána kapitola o makrech. Ještě není dokončená.)
 
Návratovou hodnotou funkce bude hodnota 720.
 
==Makra==
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===
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 <code>quote</code>, který vrátí následný výraz tak jak mu ho předáme — žádnou část nevyhodnotí. Jako syntaktický cukr můžeme použít apostrof <code>'</code>.
 
<source lang="lisp">
;; 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)
</source>
 
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 <code>unquote</code> a <code>quasiquote</code>. Quasiquote se chová stejně jako quote, akorát s tím rozdílem, že ve svém těle umožňuje použít unquote, který vyhodnotí daný výraz. Syntaktický cukr pro unquote je čárka <code>,</code> a pro quasiquote zpětný apostrof <code>`</code>.
 
<source lang="lisp">
> `(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)
</source>
 
===Základní práce s makry===
Makra se vytvářejí pomocí speciálního operátoru <code>defmacro</code>. Nejjednodušší příklad může být definice vlastní podmínky, vlastního ifu. Pomocí makra by to vypadalo následovně:
 
<source lang="lisp">
(defmacro my-if (cond true false)
`(if ,cond
,true
,false))
</source>
 
Makro se chová stejně jako běžný if:
 
<source lang="lisp">
> (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
</source>
 
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 vyhodnotili při volání funkce a tím pádem by se vždy vyhodnotili obě větve podmínky.
 
<source lang="lisp">
(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
</source>
 
===Problémy spojené s makry===
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:
 
<source lang="lisp">
; 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
</source>
 
Naivní implementace by mohla vypadat takto:
 
<source lang="lisp">
(defmacro if-false-1 (cond false)
`(if ,cond
,cond
,false))
</source>
 
Toto makro zdánlivě funguje. Ovšem do doby, než na něj pustíme kód s vedlejším efektem:
 
<source lang="lisp">
;; 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
</source>
 
Kód <code>(incf a)</code> se v těle makra vyhodnotil dvakrát, proto nám to vrátí trojku. Kód po makroexpanzi vypadá takto:
 
<source lang="lisp">
(LET ((A 1))
(IF (INCF A)
(INCF A)
'NIC))
</source>
 
Řešením je navázet vyhodnocenou podmínku na nějaký symbol:
 
<source lang="lisp">
(defmacro if-false-2 (cond false)
`(let ((cond-help ,cond))
(if cond-help
cond-help
,false)))
</source>
 
Teď už se výraz vyhodnotí pouze jednou:
 
<source lang="lisp">
> (let ((a 1))
(if-false-2 (incf a) 'nic))
2
</source>
 
==Externí odkazy==
311

editací