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