Makra 1
Opakování (kvazikvotování)
'(1 2 3 4 5) ;=> (1 2 3 4 5)
'(1 (+ 2 3) 4 5) ;=> (1 (+ 2 3) 4 5)
'(1 ,(+ 2 3) 4 5) ;=> (1 5 4 5)
'(1 ((,(+ 2 3))) 4 5) ;=> (1 ((5)) 4 5)
(define s '(a b c))
'(1 ,s 2) ;=> (1 (a b c) 2)
'(1 ,@s 2) ;=> (1 a b c 2)
'(1 '2 3) ;=> (1 (quote 2) 3)
''(1 2 3) ;=> (quote (1 2 3))
''(1 2 3) ;=> (quasiquote (1 2 3))
'(1 '(2 3)) ;=> (1 (quasiquote (2 3)))
'(1 '(,(+ 1 2) 3)) ;=> (1 (quasiquote ((unquote (+ 1 2)) 3)))
'(1 ,'(,(+ 1 2) 3)) ;=> (1 (3 3))
Problém: chceme upravit if tak,
aby při absenci alternativního výrazu vracel #f
nyní máme:
(if (= 1 2) 'blah) ;=> nedefiovaná hodnota
chceme:
(new-if (= 1 2) 'blah) ;=> #f
nabízí se vyřesit pomocí nové procedury:
(define new-if
(lambda (elem1 elem2)
(if elem1 elem2 #f)))
při volání new-if je vždy vyhodnocen i druhý argument:
(new-if #f blah-blah) ;=> Error
Potřebujeme během vyhodnocování každý výraz tvaru
(new-if expr1 expr2)
nahradit výrazem
(if
expr1
expr2
#f
bez toho aniž by se
vyhodnocovalo
expr
expr
Jinými slovy potřebujeme zavést předpis, ktery bude provádět transformaci kódu
transformaci
konkrétni
část
kód
j
nahrazen
jino
p
transformac
proběhn
vyhodnocen
transformovanéh
kód
(defin
11
(new-i
(even
x
(
1)
transformac
(i
(even
x
(
1
#f
vyhodnocen
#
Jak se dívat na transformaci?
můžeme si ji představit jako (klasickou) proceduru,
které jsou předány argumenty v nevyhodnocené podobě
transformace se v některých jazycích nazývá makroexpanze
;; transformační procedura pro new-if
(define new-if-trans
(lambda (test expr . alt)
(list 'if test expr
(if (null? alt)
#f
(car alt)))))
(new-if-trans 'e1 'e2 'e3) ;=> (if e1 e2 e3)
(new-if-trans 'e1 'e2) ;=> (if e1 e2 #f)
(new-if-trans '(even? x) '(+ x 1))
;=> (if (even? x) (+ x 1) #f)
kratsí řesení pomocí kvazikvotování
(define new-if-trans
(lambda (test expr . alt)
'(if ,test
,expr
(begin #f ,@alt))))
příklady transformace:
(new-if-trans 'expr1 'expr2 'expr3)
;=> (if expr1 expr2 (begin #f expr3))
(new-if-trans 'expr1 'expr2)
;=> (if expr1 expr2 (begin #f))
(new-if-trans '(even? x) '(+ x 1))
;=> (if (even? x) (+ x 1) (begin #f))
nouzové
řesení
new-if
které
se již chová jak má
manuáln
spustěn
transformačn
procedur
následn
vyhodnocen
transformovanéh
výraz
(eval
(new-if-trans
'(even?
x
'(
1))
Výhod
řesení
pokud
je
první
výraz
nepravdivý
alternativní
výraz
není
vyhodnocen
touto
konstrukcí
(new-if
lz
zastavi
rekurz
Nevýhod
řesení
* vsechný předávané argumentý musíme explicitně kvotovat
* transformovaný výraz musíme ručně vyhodnotit pomocí eval
* eval ve větsině interpretů pracuje jen v globálním prostředí
* volání je nepřehledné
problém s lexikálními vazbami
(let ((x 10))
(eval (new-if-transformer '(even? x) '(+ x 1))))
;=> error: x not bound
částečné řesení: použití (the-environment)
ve větsině interpretů nebude fungovat
(let ((y 10))
(eval (new-if-transformer '(even? y) '(+ y 1))
(the-environment)))
navíc jsme se nezbavili nepřehledného kódu
Řesení problému: zavedení maker
MAKRA
dva základní pohledy na makra
1 POHLED Makra jsou rozsířením syntaxe jazyka
makro
dáno
denic
svéh
transformačníh
předpis
p
načten
výraz
(READ
j
ně
proveden
makroexpanz
tut
fáz
provád
tzv
preproceso
a
p
dokončen
expanz
vsec
make
nastáv
vyhodnocován
výraz
nem
smys
uvažova
poje
aplikac
makra
takt
n
makr
pohlíž
větsin
PJ
C
DrScheme
Commo
LISP,
Výhod
přístupu
preproceso
vlastn
eva
jso
zcel
nezávisl
preproceso
můž
bý
aktivová
okamžit
p
načten
výraz
umož¬uj
snadno
kompilac
kód
(
kompilované
kód
ji
pochopiteln
žádn
makr
nejsou
Nevýhod
přístupu
makr
jso
mim
jazyk
(čast
s
zapisuj
odlisně
třeb
C
makr
nejso
element
prvníh
řád
MAKR
dv
základn
pohled
n
makr
2
POHLED
Makr
jso
speciáln
element
jazyka
makr
elemen
jazyk
obsahujíc
ukazate
n
transf
procedur
transformačn
procedur
klasick
procedur
j
potřeb
rozsíři
eval
případ
kd
s
prvn
prve
seznam
vyhodnot
n
makr
makr
jso
uživatelsk
denovan
speciáln
formy
takt
n
makr
budem
pohlíže
m
(dál
třeb
PJ
M4
TEX
Výhod
přístupu
makr
jso
element
prvníh
řád
makr
lz
pracova
jak
daty
moho
dynamick
vznikat/zanika
z
běh
program
můžem
uvažova
koncep
anonymníh
makra
Nevýhody přístupu makroexpanze dochází
a
př
činnost
eva
praktick
znemož¬uj
účinno
kompilac
kód
př
neuvážené
používán
make
komplikuj
laděn
program
AMotivační příklad
defiice
make
(define-macr
new-i
(lambd
(
(lis
'i
#
(ca
))))
;
new-if
pomoc
kvazikvotován
(define-macr
new-i
(lambd
(
'(i
,)))
(KI
U
Olomouc
P
2A
Lekc
Makr
1
3
Příkla
použit
makr
;
new-if
pomoc
kvazikvotován
(define-macr
new-i
(lambd
(
'(i
,)))
(le
((
10)
(new-i
(even
x
(
1))
aktivac
transformačn
procedur
makr
(i
(even
x
(
1
(begi
#f)
vyhodnocen
výraz
prostředí
kd
m
vazb
1
1
(KI
U
Olomouc
P
2A
Lekc
Makr
1
3
Rozsíření EVAL
Eval[E;P]:
(A) Pokud je E cislo, . . . jako obvykle
(B) Pokud je E symbol, . . . jako obvykle
(C) Pokud je E seznam tvaru (E1 E2 En), pak nejprve provedeme
vyhodnocení prvního prvku E1 v prostředí P a výslednou hodnotu
označíme F1, to jest F1 := Eval[E1;P]. Mohou nastat čtyři situace:
(C.1) Pokud F1 je procedura, . . . jako obvykle
(C.2) Pokud F1 je speciální forma, . . . jako obvykle
(C.3) Pokud F1 je makro jehož transformační procedura je T, pak
1 F0 := Apply[T
;
E2; : : : ;
En]
(F0 je výsledkem aplikace transf. procedury na nevyhodnocené arg.)
2 Výsledek vyhodnocení F elementu E v prostředí P je denován
F := Eval[F0
;P]
(F je výsledek vyhodnocení elementu F0 v prostředí P).
(C.e) Pokud F1 není procedura, speciální forma, ani makro,
pak vyhodnocení končí chybou CHYBA: První prvek seznamu . . . .
(KI, UP Olomouc) PP 2A, Lekce 3 Makra I 13 / 35
Ladění maker: základní princip
potlačíme vyhodnocení transformovaného kódu
využívá dodatečné KVOTOVÁNÍ
(define-macro new-if
(lambda (test expr . alt)
(list 'quote
(list 'if test expr
(if (null? alt) #f (car alt))))))
(new-if #f blah-blah) ;=> (if #f blah-blah #f)
(define-macro new-if
(lambda (test expr . alt)
''(if ,test ,expr (begin #f ,@alt))))
(new-if #f blah-blah) ;=> (if #f blah-blah (begin #f))
(KI, UP Olomouc) PP 2A, Lekce 3 Makra I 14 / 35
chceme vytvořit and2 dvou argumentů vracející #t nebo #f
chceme vytvořit pouze s pomocí if
;; nedostačující řesení pomocí procedury:
(define and2
(lambda (elem1 elem2)
(if elem1
(if elem2
#t
#f)
#f)))
předchozí má vážný nedostatek:
(and2 #f blah-blah) ;=> error (chceme #f)
(KI, UP Olomouc) PP 2A, Lekce 3 Makra I 15 / 35
and
s
dvěm
argument
vracejíc
konjunkc
potřebujeme
běhe
vyhodnocován
každ
výra
(and
expr
expr2
nahradi
výraze
(i
expr
(i
expr
#
#f
#f
be
toh
ani
b
s
vyhodnocoval
expr
expr
esen
pomoc
makra
(define-macr
and
(lambd
(expr
expr2
'(i
,expr
(i
,expr
#
#f
#f))
(KI
U
Olomouc
P
2A
Lekc
Makr
1
3
Ukázky použití and2
(and2 1 (+ 1 2))
+
(if 1 (if (+ 1 2) #t #f) #f)
+
#t
(and2 #f blah-blah)
+
(if #f (if blah-blah #t #f) #f)
+
#f
(and2 #t #f)
+
(if #t (if #f #t #f) #f)
;=> #f
Anaforický if: if*
if*, který pracuje stejně jako if, ale umož¬uje v druhém a třetím
výrazu používat symbol $result, který bude vždy navázaný na
výsledek vyhodnocení prvního výrazu
praktické rozsíření, místo:
(if (member 'b '(a b c d))
(list 'nalezen (member 'b '(a b c d)))
'blah)
stačí napsat:
(if* (member 'b '(a b c d))
(list 'nalezen $result)
'blah) ;=> (nalezen (b c d))
Řešení: během vyhodnocování každý výraz
(if expr1 expr2 expr3)
potřebujeme nahradit výrazem:
(let
(($resul expr1)
(if $resul expr2 expr3)))
t
opě
be
vyhodnocován
expr
a
expr
if
jak
makr
(define-macr
if
(lambd
(tes
exp
alt
'(le
(($resul
,test)
(i
$resul
,exp
,@alt)))
Ukázka použití
if bez $result se chová jako normální if
(if
3
(le
(($resul
1)
(i
$resul
3)
příklad použití $result
(if
$resul
3
(le
(($resul
1)
(i
$resul
$resul
3)
Složitějsí ukázka použití if
(if
(membe
'
'(
d)
(lis
'naleze
$result
'blah
(le
(($resul
(membe
(quot
b
(quot
(
d))))
(i
$resul
(lis
(quot
nalezen
$result
(quot
blah))
(naleze
(
d)
Vsimněte si expandované výrazy
nejsou
žádné
''
(KI
U
Olomouc
P
2A
Lekc
Makr
2
3
i
pomoc
con
(define-macr
i
(lambd
(tes
exp
alt
'(con
(,tes
,expr
(els
,alt)))
i
pomoc
con
(be
nutnost
mí
alternativn
větev
(define-macr
i
(lambd
(tes
exp
alt
'(con
(,tes
,expr
(els
(begi
#
,@alt))))
podobn
jak
předchozí
al
vracím
nedenovano
hodnot
(define-macr
i
(lambd
(tes
exp
alt
'(con
(,tes
,expr
(els
(begi
(cond
,@alt))))
(KI
U
Olomouc
P
2A
Lekc
Makr
2
3
con
pomoc
i
musím
přepsa
jede
cond-výra
pomoc
několik
if
;
základn
con
pomoc
i
(pomoc
rekurzivníh
vnoření
(define-macr
con
(lambd
clis
(le
dive-if
((clis
clist)
(i
(null
clist
'(i
#
#f
(i
(equal
(caa
clist
'else
(cada
clist
'(i
,(caa
clist
,(cada
clist
,(dive-if
(cd
clist)))))))
(KI
U
Olomouc
P
2A
Lekc
Makr
2
3
Příkla
použit
(con
((
3
'blah
((
10
(
x)
((prop
y
(lis
y)
(els
(
20))
(i
(
3
(quot
blah
(i
(
10
(
x
(i
(prop
y
(lis
y
(
20)))
(KI
U
Olomouc
P
2A
Lekc
Makr
2
3
con
pomoc
i
musím
přepsa
jede
cond-výra
pomoc
několik
if
;
základn
con
pomoc
i
(řesen
jak
rekurzivn
makro
(define-macr
con
(lambd
clis
(i
(null
clist
'(i
#
#f
(i
(equal
(caa
clist
'else
(cada
clist
'(i
,(caa
clist
,(cada
clist
(con
,@(cd
clist))))))
(KI
U
Olomouc
P
2A
Lekc
Makr
2
3
Příkla
použit
(con
((
3
'blah
((
10
(
x)
((prop
y
(lis
y)
(els
(
20))
(i
(
3
(quot
blah
(con
((
10
(
x)
((prop
y
(lis
y)
(els
(
20)))
(KI
U
Olomouc
P
2A
Lekc
Makr
2
3
Rozsířen
verz
defin
defin
jsm
zatí
používal
pouz
v
tvar
(defin
symbo
/vyra
.
R6R
Schem
j
defin
zavede
tak
v
tvar
(defin
(symbo
/argument
....
/vyraz
....
Příklad
faktoriá
(defin
(
n
(i
(
1
(
(
(
1))))
Příklad
nepovinn
argument
(defin
(
args
(lis
args)
(KI
U
Olomouc
P
2A
Lekc
Makr
2
3
Rozsířená verze define
Pokud by nás interpret nedisponoval rozsířeným define,
pak bychom jej mohli vyrobit jako makro:
(define-macro def
(lambda (first . args)
(if (symbol? first)
'(define ,first ,@args)
'(define ,(car first)
(lambda ,(cdr first)
,@args)))))
Příklad použití:
(def (f n)
(if (= n 1)
1
(* n (f (- n 1)))))
(f 6) ;=> 720
(KI, UP Olomouc) PP 2A, Lekce 3 Makra I 28 / 35
Konjunkce libovolně mnoha argumentů pomocí if
;; základní verze
(define-macro and
(lambda args
(if (null? args)
#t
'(if ,(car args)
(and ,@(cdr args))
#f))))
(and 1 2 3) ;=> #t
protože:
(and 1 2 3) ;=> (if 1 (and 2 3) #f) Z=)
(and 2 3) ;=> (if 2 (and 3) #f) Z=)
(and 3) ;=> (if 3 (and) #f) Z=)
(and) ;=> #t Z=) #t
(KI, UP Olomouc) PP 2A, Lekce 3 Makra I 29 / 35
Konjunkce libovolně mnoha argumentů pomocí if
;; zlepsená verze (zobecněné pravdivostní hodnoty)
(define-macro and
(lambda args
(if (null? args)
#t
(if (null? (cdr args))
(car args)
'(if ,(car args)
(and ,@(cdr args))
#f)))))
nyní se již chová jako klasický and:
(and) ;=> #t
(and 1 2 3) ;=> 3
(and 1 #f 3) ;=> #f
(KI, UP Olomouc) PP 2A, Lekce 3 Makra I 30 / 35
Disjunkce libovolně mnoha argumentů pomocí if
;; základní verze
(define-macro or
(lambda args
(if (null? args)
#f
'(if ,(car args)
#t
(or ,@(cdr args))))))
Příklad použití:
(or) ;=> #f
(or 1 2 3) ;=> #t chtěli bychom 1
(or #f 2 3) ;=> #t chtěli bychom 2
(KI, UP Olomouc) PP 2A, Lekce 3 Makra I 31 / 35
Disjunkce libovolně mnoha argumentů pomocí if
;; rozsířená verze
(define-macro or
(lambda args
(if (null? args)
#f
(if (null? (cdr args))
(car args)
'(if ,(car args)
,(car args)
(or ,@(cdr args)))))))
Chová se (zdánlivě) v pořádku:
(or) ;=> #f
(or 1 2 3) ;=> 1
(or #f 2 3) ;=> 2
(KI, UP Olomouc) PP 2A, Lekce 3 Makra I 32 / 35
Problémy s implementací disjunkce pomocí if
nase implementace or: dvakrát vyhodnocuje pravdivé argumenty
(kromě posledního)
důsledek: nechová se jako klasický or pokud použijeme vedlejsí efekt
Chování klasického or
(let ((x 0))
(or (begin (set! x (+ x 1))
x)
blah)) ;=> 1
Chování naseho or:
(let ((x 0))
(or (begin (set! x (+ x 1))
x)
blah)) ;=> 2
(KI, UP Olomouc) PP 2A, Lekce 3 Makra I 33 / 35
Problémy s implementací disjunkce pomocí if
;; pokus o řesení předchozího problému
(define-macro or
(lambda args
(if (null? args)
#f
(if (null? (cdr args))
(car args)
'(let ((result ,(car args)))
(if result
result
(or ,@(cdr args))))))))
Nyní už jsme předchozí problém odstranili, . . .
(let ((x 0))
(or (begin (set! x (+ x 1)) x)
blah)) ;=> 1
. . . ale nový problém jsme vyrobili
Klasický or:
(let ((result 10))
(or #f result)) ;=> 10
Náš or:
(let ((result 10))
(or #f result))
+
(let ((result #f))
(if result result (or result))) ;=> #f
doslo k překrytí symbolu result symbolem stejného jména,
který je používán uvnitř naseho makra
tomuto efektu se říká symbol capture (variable capture)
v dalsí lekci ukážeme čisté řesení tohoto problému