Internet Info, s.r.o. Lupa Měšec Podnikatel Root Zdroják DigiZone Slunečnice Vitalia TopDrive KupDnes Navrcholu NovýTarif Dobrý web Weblogy Woko Jagg Computer.cz SK: MojeLinky

Hlavní navigace

LISPová makra aneb programovatelný programovací jazyk

LISP je velmi mocný programovací jazyk, disponující důležitou vlastností, která jej odlišuje a vyzdvihuje nad ostatní jazyky. Tou vlastností jsou LISPová makra. Jak ve skutečnosti vypadají a co dokáží? Jak se liší od maker například v C? V článku si odpovíme na tyto a další otázky a předvedeme si i praktické ukázky.

Tweetni to Twitter Jaggni to! Jagg Del.icio.us Delicious

Před nějakou dobou jsem na tomto serveru publikoval článek o programovacím jazyce LISP. Nyní jsem si uvědomil, že jsem v něm nepopsal stěžejní vlastnost, která vyzdvihuje LISP nad ostatní programovací jazyky: LISPová makra. Tento článek navazuje na můj předchozí, pokud tedy nebudete vědět „která bije“, přečtěte si nejprve můj úvod do LISPu. Stejně jako minule budu používat Common Lisp.

První věc, kterou bych chtěl vyjasnit, je samotný pojem „makro“: valná většina lidí si pod pojmem „makro“ v souvislosti s programováním vybaví makra preprocesoru jazyka C. LISPová makra nemají s těmito makry nic společného: fungují úplně jinak. Menší část lidí si představí makra textového editoru: ani tady není představa přiléhavá LISPovým makrům. Žádám vás tedy, abyste „osvobodili svoji mysl“ a pojmu „makro“ v tomto článku nedávali konotace Céčkového či preprocesorové­ho makra.

Jednoduchá definice LISPového makra je následující: makro je konstrukce, které předáme (jako argumenty) nevyhodnocované s-výrazy a ono z nich sestaví vlastní LISPový kód (přitom může používat všechny funkce LISPu, i námi definované) a tento kód potom zavolá na místě, kde je zapsáno. Definuje se pomocí formy DEFMACRO, která „vypadá“ jako DEFUN.

Hmmm… ale co si pod tím představit? Nejjednodušší příklad, na kterém „vám to dojde“, je následující:

[1]>(defmacro pozadu (x) (reverse x) )
POZADU
[2]>(pozadu (1 4 -) )
3

Makro POZADU nejdřív otočilo námi zadaný s-výraz a pak ho teprve spustilo: nadefinovali jsme si tedy vlastní syntaktickou konstrukci, v podstatě vlastní jazyk nad LISPem, který akceptuje LISPové výrazy zapsané obráceně. Všimněte si, že (1 4 -) je sice platný s-výraz, ale není platná LISPová forma (tedy s-výraz, který se dá přímo vyhodnotit) – a přesto jej můžeme, neuvozený apostrofem, napsat, protože makro své argumenty nevyhodnocuje.

Na sestavení výsledného kódu „uvnitř“ makra můžeme samozřejmě použít všechny „list-sestavující“ funkce jako LIST, CONS a podobně, ale jazyk nám nabízí výhodnou syntaktickou zkratku. Je jí konstrukce se zpětným apostrofem (backquote). Tato konstrukce je ve skutečnosti jistým druhem makra, což nám ukazuje, jak jsou makra užitečná. K věci: seznam uvozený zpětným apostrofem se nevyhodnocuje, až na ty jeho položky, které jsou uvozeny čárkou. Pomocí „,@“ vyhodnotíme položku tak, že její obsah se vnoří do původního okolo stojícího seznamu.

Příklad:

[3]> (setq a 1 b 2 c '(x y z) )
(X Y Z)
[4]> `(a b c)
(A B C)
[5]> `(a ,b c)
(A 2 C)
[6]> `(,a b ,c)
(1 B (X Y Z))
[7]> `(,a b ,@c)
(1 B X Y Z)

Takže to by byla základní teorie, nyní přistoupíme k prvnímu příkladu: standard LISPu definuje speciální formu IF tak, že bere podmínku, výraz, který se vyhodnotí, když je pravdivá a druhý, který se vyhodnotí, když je nepravdivá. Často však chceme provést, pokud byla podmínka pravdivá, více výrazů. Museli bychom použít speciální formu PROGN, která nám tyto výrazy „obalí“ tak, že se IFu budou jevit jako jeden výraz: (if podminka (progn (udelej-tohle) (a-tamto) (jeste-toto))). Protože stále opakovat idiom IF PROGN může být úmorné, definuje Common Lisp makro WHEN, které se nám expanduje do IF PROGN – takže můžeme psát (when podminka (udelej-tohle) (a-tamto) (jeste-toto)). Pokud by WHEN v jazyce nebylo, mohli bychom si ho sami definovat:

(defmacro my-when (podminka &rest telo)
`(if ,podminka (progn ,@telo)))

Abychom nedefinovali pouze to, co v LISPu už je, ukážu vám, jak si nadefinovat klasický cyklus WHILE, který ve standardu Common Lispu není:

(defmacro while (podminka &rest telo)
`(tagbody
    looping
      (if (not ,podminka) (go konec) )
       ,@telo
      (go looping)
     konec))

Pokud v nějakém jiném jazyce chceme syntaxi, která tam není, musíme prosit tvůrce jazyka. Ne tak v LISPu: tam si můžeme takovou syntaxi nadefinovat sami.

Uvědomme si, že takovou syntaktickou konstrukcí, kterou můžeme makrem abstrahovat, je třeba i definice funkce. Můžeme tedy nadefinovat makro, které pokud se spustí, nám nadefinuje jistý druh funkce.

Nadefinujeme makro DEF-SHELL-FUNC, kterému předáme název funkce a řetězce a ono nám nadefinuje funkci, která pokud ji pustíme, spustí tyto řetězce jako příkazy UNIXového shellu. Příklad je víceméně pouze ilustrační, ale i taková věc se může někdy hodit:

(defmacro def-shell-func (jmeno &rest prikazy)
    (setf cele-prikazy nil)
    (dolist (p prikazy)
      (setf cele-prikazy (append cele-prikazy (list `(run-shell-command ,p)))))
    `(defun ,jmeno ()
        ,@cele-prikazy ))

potom

(def-shell-func compile-src
    "cd src"
    "make all"
    "cd .."
    "echo hotovo")

nám nadefinuje funkci COMPILE-SRC, která při svém spuštění provede zadané shellové příkazy.

Programování maker má některá úskalí – jednoduchý příklad: známe již speciální formu PROGN, která spustí formy a vrátí výsledek poslední z nich. Existuje též makro PROG1, které se chová stejně, ale vrátí hodnotu první ze zadaných forem. Pokud by náš LISP makro PROG1 neobsahoval, mohli bychom zkusit jej nadefinovat sami. Myšlenka je jednoduchá: hodnotu první formy někam uložíme a na konci vrátíme:

(defmacro my-prog1 (prvni &rest ostatni)
   `(progn
        (setq nekam ,prvni)
        ,@ostatni
        nekam
     ))

Ve velké, velmi velké většině případů bude makro fungovat, ovšem pokud někoho napadne použít proměnnou se jménem NEKAM – dle Murphyho zákona to někdo určitě udělá -, dopadne to takhle:

[16]> (my-prog1 1 2 3)
1
[17]> (my-prog1 1 2 (setq nekam 10) 3)
10

Používat velmi obskurní názvy proměnných je řešení velmi „špinavé“ a nesystémové, navíc jméno proměnné by mělo označovat její účel a ne být divné a kryptické. Musíme si uvědomit, že jména takovýchto proměnných se nesmí objevit ve vygenerovaném kódu, použití jakýchkoli proměnných v samotném makru („venku“) nevadí. Tady nám pomůže funkce GENSYM, která vygeneruje symbol, u něhož je zaručeno, že není použitý. GENSYM se pustí v okamžiku expanze makra, a použije se tedy jméno, které v okamžiku expanze nikdo nepoužívá. Viz příklad správného my-prog1

(defmacro my-prog1 (prvni &rest ostatni)
   (let ((nekam (gensym)))
   `(progn
        (setq ,nekam ,prvni)
        ,@ostatni
        ,nekam
     )))

Nyní se nám „,nekam“ expanduje na námi vytvořené jméno proměnné, které bude v těle makra… Pro zajímavost: viděl jsem v jedné knížce definované makro WITH-GENSYMS, které nám zvládne abstrahovat konstrukce vyžadující GENSYM, podobné jako v nové definici MY-PROG1.

Dále si musíme dát pozor, aby se nám některá forma nevyhodnocovala vícekrát než uživatel makra očekává: to se stává, pokud v expandovaném kódu sestavíme ten samý výraz na více místech.

davame_internetu_obsah
       

Pokud si nejste jisti, jak se makro expanduje, použijte funkci MACROEXPAND-1, které zadáte formu (quotovanou, samozřejmě) a ona nám ji rozexpanduje. Ta „1“ je tam proto, že funkce „MACROEXPAND“ bez jedničky expanduje makro „až na doraz“, tedy do největší hloubky tak, aby zůstaly pouze funkce a speciální formy.

To je vše – doufám, že si z mého článku vezmete inspiraci, jak lépe programovat.

Školení: Linux – Firewall, Samba, VPN

Na třídenním školení se naučíte nainstalovat a spravovat Firewall a Router, SAMBA Doménový a Souborový server. Dále si zprovozníte vlastní, zabezpečený VPN server.

Podrobnější informace a přihláška

Ohodnoťte jako ve škole:
Průměrná známka 2,96

Přehled názorů

with-gensyms
Blackened 7. 8. 2007 00:23
Nový
└ 
Re: with-gensyms
disorder 21. 8. 2007 00:48
Nový
ma_titulek(X) :- nazor(X)
Rejpal 7. 8. 2007 01:43
Nový
└ 
Re: ma_titulek(X) :- nazor(X)
tom 7. 8. 2007 08:36
Nový
 
└ 
Re: ma_titulek(X) :- nazor(X)
Rejpal 7. 8. 2007 11:48
Nový
 
 
└ 
Re: ma_titulek(X) :- nazor(X)
tom 7. 8. 2007 11:54
Nový
 
 
 
└ 
Re: ma_titulek(X) :- nazor(X)
Rejpal 7. 8. 2007 12:00
Nový
infix?
Jaroslav Hájek 7. 8. 2007 09:28
Nový
├ 
Re: infix?
xxx 7. 8. 2007 09:49
Nový
├ 
Re: infix?
broukoid 7. 8. 2007 10:57
Nový
│
└ 
Re: infix?
Rejpal 7. 8. 2007 11:55
Nový
└ 
Re: infix?
Rejpal 7. 8. 2007 11:46
Nový
 
└ 
Re: infix?
broukoid 7. 8. 2007 12:23
Nový
 
 
└ 
Re: infix?
Rejpal 7. 8. 2007 12:58
Nový
jednoduchá pravda
Indián 7. 8. 2007 13:31
Nový
Nové syntaktické konstrukce v jazyku
Pavel Tišnovský 7. 8. 2007 14:08
Nový
Moj prispevok k uzitocnym makram...
Michaelson 7. 8. 2007 14:12
Nový
Lisp a LSD
anonymní uživatel 7. 8. 2007 17:16
Nový
├ 
Re: Lisp a LSD
anonymní uživatel 7. 8. 2007 17:17
Nový
├ 
Re: Lisp a LSD
D 7. 8. 2007 17:40
Nový
└ 
Re: Lisp a LSD
VViki 7. 8. 2007 18:25
Nový
 
├ 
Re: Lisp a LSD
Rejpal 7. 8. 2007 19:21
Nový
 
│
└ 
Re: Lisp a LSD
VViki 8. 8. 2007 10:14
Nový
 
├ 
Re: Lisp a LSD
Rejpal 7. 8. 2007 19:29
Nový
 
│
└ 
Re: Lisp a LSD
VViki 8. 8. 2007 10:19
Nový
 
│
 
├ 
Re: Lisp a LSD
Rejpal 8. 8. 2007 10:45
Nový
 
│
 
│
├ 
Re: Lisp a LSD
VViki 8. 8. 2007 12:09
Nový
 
│
 
│
│
└ 
Re: Lisp a LSD
Rejpal 8. 8. 2007 12:31
Nový
 
│
 
│
│
 
└ 
Re: Lisp a LSD
VViki 8. 8. 2007 13:02
Nový
 
│
 
│
│
 
 
├ 
Re: Lisp a LSD
Rejpal 8. 8. 2007 15:07
Nový
 
│
 
│
│
 
 
└ 
Re: Lisp a LSD
xxx 9. 8. 2007 01:07
Nový
 
│
 
│
├ 
Re: Lisp a LSD
Petr 8. 8. 2007 18:25
Nový
 
│
 
│
│
└ 
Re: Lisp a LSD
Rejpal 8. 8. 2007 19:25
Nový
 
│
 
│
└ 
Re: Lisp a LSD
mys elf 8. 8. 2007 21:35
Nový
 
│
 
│
 
├ 
Re: Lisp a LSD
Rejpal 8. 8. 2007 21:53
Nový
 
│
 
│
 
│
└ 
Re: Lisp a LSD
mys elf 8. 8. 2007 22:47
Nový
 
│
 
│
 
└ 
Re: Lisp a LSD
Michaelson 8. 8. 2007 22:15
Nový
 
│
 
│
 
 
├ 
Re: Lisp a LSD
Michaelson 8. 8. 2007 22:26
Nový
 
│
 
│
 
 
└ 
Re: Lisp a LSD
mys elf 8. 8. 2007 22:53
Nový
 
│
 
│
 
 
 
└ 
Re: Lisp a LSD
Michaelson 8. 8. 2007 23:27
Nový
 
│
 
│
 
 
 
 
└ 
Re: Lisp a LSD
mys elf 8. 8. 2007 23:52
Nový
 
│
 
│
 
 
 
 
 
├ 
Re: Lisp a LSD
anonymní uživatel 9. 8. 2007 00:19
Nový
 
│
 
│
 
 
 
 
 
│
├ 
Re: Lisp a LSD
Michaelson 9. 8. 2007 00:47
Nový
 
│
 
│
 
 
 
 
 
│
│
└ 
Re: Lisp a LSD
Jirka P 9. 8. 2007 12:32
Nový
 
│
 
│
 
 
 
 
 
│
│
 
└ 
Re: Lisp a LSD
Rejpal 9. 8. 2007 14:07
Nový
 
│
 
│
 
 
 
 
 
│
│
 
 
└ 
Re: Lisp a LSD
anonymní uživatel 9. 8. 2007 21:12
Nový
 
│
 
│
 
 
 
 
 
│
│
 
 
 
└ 
Re: Lisp a LSD
Pichi 10. 8. 2007 10:43
Nový
 
│
 
│
 
 
 
 
 
│
└ 
Re: Lisp a LSD
mys elf 9. 8. 2007 00:53
Nový
 
│
 
│
 
 
 
 
 
│
 
└ 
Re: Lisp a LSD
Michaelson 9. 8. 2007 01:15
Nový
 
│
 
│
 
 
 
 
 
│
 
 
└ 
Re: Lisp a LSD
Michaelson 9. 8. 2007 01:36
Nový
 
│
 
│
 
 
 
 
 
└ 
Re: Lisp a LSD
Petrjr 10. 8. 2007 02:46
Nový
 
│
 
│
 
 
 
 
 
 
├ 
Re: Lisp a LSD
Petrjr 10. 8. 2007 03:53
Nový
 
│
 
│
 
 
 
 
 
 
└ 
Re: Lisp a LSD
anonymní uživatel 10. 8. 2007 12:25
Nový
 
│
 
│
 
 
 
 
 
 
 
└ 
Re: Lisp a LSD
Petrjr 10. 8. 2007 14:24
Nový
 
│
 
├ 
Re: Lisp a LSD
anonymní uživatel 8. 8. 2007 11:25
Nový
 
│
 
│
└ 
Re: Lisp a LSD
VViki 8. 8. 2007 12:19
Nový
 
│
 
│
 
├ 
Re: Lisp a LSD
Pavel Szalbot 8. 8. 2007 13:32
Nový
 
│
 
│
 
│
└ 
Re: Lisp a LSD
VViki 8. 8. 2007 14:30
Nový
 
│
 
│
 
│
 
├ 
Re: Lisp a LSD
Jakub Květák 8. 8. 2007 16:06
Nový
 
│
 
│
 
│
 
│
├ 
Re: Lisp a LSD
VViki 8. 8. 2007 16:25
Nový
 
│
 
│
 
│
 
│
│
└ 
Re: Lisp a LSD
Rejpal 8. 8. 2007 16:39
Nový
 
│
 
│
 
│
 
│
└ 
Re: Lisp a LSD
mys elf 8. 8. 2007 21:20
Nový
 
│
 
│
 
│
 
└ 
Re: Lisp a LSD
deda.jabko 8. 8. 2007 18:11
Nový
 
│
 
│
 
└ 
Re: Lisp a LSD
tng 9. 8. 2007 00:28
Nový
 
│
 
└ 
Re: Lisp a LSD
gregy 11. 8. 2007 09:57
Nový
 
├ 
Re: Lisp a LSD
BoneFlute 7. 8. 2007 20:05
Nový
 
│
└ 
Re: Lisp a LSD
mys elf 7. 8. 2007 20:30
Nový
 
│
 
└ 
Re: Lisp a LSD
BoneFlute 7. 8. 2007 20:51
Nový
 
├ 
Re: Lisp a LSD
Guzmír 7. 8. 2007 21:22
Nový
 
│
└ 
Re: Lisp a LSD
VViki 8. 8. 2007 12:31
Nový
 
└ 
Re: Lisp a LSD
Biktop 8. 8. 2007 16:10
Nový
Cas aplikace makra
anonymní uživatel 7. 8. 2007 21:51
Nový
└ 
Re: Cas aplikace makra
Rejpal 7. 8. 2007 22:50
Nový
       

Tento text je již více než dva měsíce starý. Chcete-li na něj reagovat v diskusi, pravděpodobně vám již nikdo neodpoví. Pro řešení aktuálních problémů doporučujeme využít naše diskusní fórum.

Zasílat nově přidané příspěvky e-mailem