-
Data: 2014-11-09 14:38:54
Temat: Re: Makra w jezyku Scheme
Od: g...@g...com szukaj wiadomości tego autora
[ pokaż wszystkie nagłówki ]* MAKRA PROCEDURALNE (define-macro)
Niektore wersje Scheme'a obsluguja proste markra proceduralne w stylu
Common Lispa. Pomysl jest taki, ze traktujemy wyrazenie wejsciowe jako
liste, i piszemy procedure, ktora przeksztalca te liste do innej listy.
Do zdefiniowania takiej formy specjalnej uzywamy specjalnej formy
define-macro o nastepujacej postaci:
: (define-macro (nazwa-makra argumenty ...)
: cialo + ...)
gdzie "cialo + ..." powinno zwracac wyrazenie, ktore ma zostac ewaluowane
(lub dalej przeksztalcone). Przyklady powinny nieco te kwestie rozjasnic.
** DEFINICJA FORMY "let"
Gwoli przypomnienia, powiedzielismy sobie w poprzednim rozdziale, ze
forma "let" powinna byc przed ewaluacja transformowana z postaci
: (let ((nazwa wartosc) ...) cialo + ...)
do postaci
: ((lambda (nazwa ...) cialo + ...) wartosc ...)
*** PRELIMINARIA -- ZWYKLA FUNKCJA TRANSFORMUJACA LISTE
Zanim przystapimy do zdefiniowania makra, sprobujmy napisac zwykla funkcje,
ktora przetransformuje liste
: (let ((nazwa-1 wartosc-1) (nazwa-2 wartosc-2)) cialo-1 cialo-2)
do postaci
: ((lambda (nazwa-1 nazwa-2) cialo-1 cialo-2) wartosc-1 wartosc-2)
Nazwijmy pierwsza z powyzszych list (te zawierajaca symbol "let") X, a druge
(te zaczynajaca sie od listy zaczynajacej sie od symbolu "lambda") -- Y.
**** DEKONSTRUKCJA LISTY WEJSCIOWEJ
Zauwazmy, ze:
: (car X) = let
: (cdr X) = (((nazwa-1 wartosc-1) (nazwa-2 wartosc-2)) cialo-1 cialo-2)
: (car (cdr X)) = ((nazwa-1 wartosc-1) (nazwa-2 wartosc-2))
: (cdr (cdr X)) = (cialo-1 cialo-2)
Dla ogolnego przypadku mozemy zatem zdefiniowac sobie:
: (define macro-body (lambda (macro) (cdr macro)))
: (define let-bindings (lambda (macro-body) (car macro-body)))
: (define let-body (lambda (macro-body) (cdr macro-body)))
Jest jasne, ze lista wiazan zwracana przez "let-bindings" ma postac
: ((nazwa wartosc) ...)
Chcielibysmy miec mozliwosc oddzielenia od siebie nazw i wartosci, tzn.
dysponowac funkcjami
: (define bindings-names (lambda (let-bindings) ??))
: (define bindings-values (lambda (let-bindings) ??))
o takich wlasnosciach, ze
: (bindings-names '((nazwa-1 wartosc-1) (nazwa-2 wartosc-2)))
da w wyniku
: (nazwa-1 nazwa-2)
zas
: (bindings-values '((nazwa-1 wartosc-1) (nazwa-2 wartosc-2)))
zwroci
: (wartosc-1 wartosc-2)
Wiemy tez, ze skoro kazde pojedyncze wiazanie ma postac
: (nazwa wartosc)
to mozemy zdefiniowac
: (define binding-name (lambda (binding) (car binding)))
: (define binding-value (lambda (binding) (car (cdr binding))))
Funkcje "bindings-names" i "bindings-values" mozemy zdefiniowac rekurencyjnie.
Jezeli lista wiazan jest pusta, to oczywiscie listy nazw [wartosci] tez beda
puste. W przeciwnym razie zwracamy pare, ktorej glowa jest nazwa [wartosc]
z glowy listy, a ogonem -- lista pozostalych nazw [wartosci]:
: (define bindings-names
: (lambda (let-bindings)
: (if (eq? let-bindings '())
: '()
: (cons (binding-name (car let-bindings))
: (bindings-names (cdr let-bindings))))))
: (define bindings-values
: (lambda (let-bindings)
: (if (eq? let-bindings '())
: '()
: (cons (binding-value (car let-bindings))
: (bindings-values (cdr let-bindings))))))
**** KONSTRUKCJA FORMY WYJSCIOWEJ
Teraz, kiedy jestesmy juz w stanie wyekstrahowac poszczegolne elementy
-- listy nazw i wartosci oraz wrazenia z ciala funkcji -- pozostaje nam
do rozwiazania jeszcze jeden problem. Pozadana przez nas lista wyjsciowa
ma miec postac
: ((lambda (nazwa-1 nazwa-2) cialo-1 cialo-2) wartosc-1 wartosc-2)
Z tego powodu nie mozemy napisac
: (list (list 'lambda <lista-nazw> <cialo-funkcji>) <lista-wartosci>)
poniewaz wowczas lista wynikowa mialaby postac
: ((lambda (nazwa-1 nazwa-2) (cialo-1 cialo-2)) (wartosc-1 wartosc-2))
Mozemy jednak uzyc funkcji "apply" w polaczeniu z funkcja "list", zeby
"splaszczyc" ostatnia liste:
: (apply list (apply list 'lambda <lista-nazw> <cialo-funkcji>) <lista-wartosci>)
Dzieki temu mamy wszystko, co niezbedne do tego, zeby przetransformowac liste
X do postaci Y:
: (define transform-let
: (lambda (macro)
: (apply list
: (apply list
: 'lambda
: (bindings-names (let-bindings (macro-body macro)))
: (let-body (macro-body macro)))
: (bindings-values (let-bindings (macro-body macro))))))
*** DEFINICJA MAKRA
Zaopatrzeni w te wiedze, mozemy jej uzyc do zdefiniowania makra:
: (define-macro (let . macro-body)
: (apply list
: (apply list
: 'lambda
: (bindings-names (let-bindings macro-body))
: (let-body macro-body))
: (bindings-values (let-bindings macro-body))))
Naglowek funkcji stanowi liste kropkowana -- to dlatego, ze nasze makro moze
przyjac dowolnie wiele argumentow. Makro rozni sie od funkcji transform-let
tym, ze w funkcji musielismy pominac sama nazwe makra ("let"), natomiast
procesor makr robi to za nas.
Widac tez, ze definiujac makro, nie uzywamy formy lambda. Przy okazji
warto dodac, ze funkcje mozna definiowac analogicznie -- zamiast pisac
: (define funkcja (lambda (argumenty ...) cialo + ...))
mozemy rowniez napisac
: (define (funkcja argumenty ...) cialo + ...)
i wowczas przed ewaluacja ta druga forma zostanie przetransformowana do tej
pierwszej.
Definicja
: (define my-list (lambda x x))
jest przy tym rownowazna definicji
: (define (my-list . x) x)
Oczywiscie, moglibysmy rowniez zdefiniowac makro przyjmujace stala ilosc
argumentow, nie uzywajac listy kropkowanej:
: (define-macro (two-argument-macro a b)
: (list 'quote (list 'argument-a: a 'argument-b: b)))
**** SPECJALNA SKLADNIA DO BUDOWANIA LIST -- FORMA "quasiquote"
Trzeba przyznac, ze postac otrzymanej przez nas funkcji przeksztalcajacej forme
"let" do wyrazenia "lambda" jest skomplikowana i trudna do czytania. Z tego
powodu hakerzy lispa wymyslili specjalna notacje ulatwiajaca budowanie
zlozonych struktur listowych.
Po pierwsze, przyjmijmy, ze -- tak jak zapisy "(quote X)" i "'X" sa rownowazne,
zapisom
: (quasiquote X)
: (unquote X)
: (unquote-splicing X)
odpowiadaja skroty
: `X
: ,X
: ,@X
"quasiquote" jest zas pewna forma specjalna (ktora mozna zdefiniowac np. przy
pomocy "define-macro"), w obrebie ktorej symbole "unquote" i "unquote-splicing"
maja dodatkowo specjalne znaczenie. Na przyklad, zapis
: `(z ,z ,@z)
jest rownowazny zapisowi
: (apply list 'z z z)
i jesli np. wartoscia symbolu "z" bedzie lista (1 2 3), to wyrazenie otrzyma
wartosc:
: (let ((z '(1 2 3)))
: `(z ,z ,@z))
: ===>
: (z (1 2 3) 1 2 3)
Element "unquote-splicing" moze sie pojawic na dowolnej pozycji, w zwiazku
z czym wartoscia wyrazenia
: (let ((z '(1 2 3)))
: `(,z ,@z z))
bedzie
: ((1 2 3) 1 2 3 z)
Takiego efektu nie moglibysmy uzyskac przy pomocy funkcji "apply" (tak naprawde
pierwsze uzycie operatora "quasiquote" tez tego nie robi. W praktyce do laczenia
list sluzy funkcja "append", ktorej definiowania postanowilem uniknac, zeby
nieco uproscic swoj i tak juz chyba nadmiernie skomplikowany wywod)
Dysponujac makrem "quasiquote", mozemy zdefiniowac nasze makro nastepujaco:
: (define-macro (let . macro-body)
: `((lambda ,(bindings-names (let-bindings macro-body))
: ,@(let-body macro-body))
: ,@(bindings-values (let-bindings macro-body))))
Zapis ten jest juz duzo krotszy i czytelniejszy od poprzedniego, choc do
zrozumienia tego makra wymagana jest znajomosc zdefiniowanych wczesniej
funkcji "let-bindings", "let-body", "binding-names" i "binding-values"
-- analiza kodu, choc stosunkowo prosta, jest mimo wszystko nietrywialna.
** DEFINICJA SPOJNIKOW LOGICZNYCH "or" i "and"
Po tym, co juz zostalo powiedziane, definicja spojnikow logicznych powinna
byc stosunkowo prosta. Zaczniemy od definicji koniunkcji:
: (define-macro (and first . rest)
: (if (eq? rest '())
: first
: `(if ,first (and ,@rest) #f)))
Jedyna nowosc, jaka sie tu pojawia, dotyczy tego, ze lista argumentow sklada
sie z dwoch symboli, przy czym pierwszy (first) zostaje zwiazany z pojedynczym
elementem, zas drugi, pojawiajacy sie po kropce (rest) -- z lista pozostalych
argumentow.
W analogiczny sposob mozemy definiowac funkcje, przy czym zapis
: (define (funkcja arg-1 ... arg-n . args) cialo + ...)
jest rownowazny zapisowi
: (define funkcja (lambda (arg1 ... arg-n . args) cialo + ...))
Spojnik "or" moglby byc zdefiniowany rownie prosto, gdybysmy nie chcieli
zachowac wartosci prawdziwego wyrazenia:
: (define-macro (or first . rest)
: (if (eq? rest '())
: first
: `(if ,first #t (or ,@rest))))
Jezeli jednak chcemy te wartosc zachowac, to -- zgodnie ze wczesniejszymi
rozwazaniami -- musimy wprowadzic nowy identyfikator, tzn. chcemy, zeby
formy o postaci
: (or p q r ...)
byly transformowane do postaci
: (let ((T p)) (if T T (or q r ...)))
dla T niewystepujacego w q, r, ...
Jednym ze sposobow byloby ustalenie pewnego T i zabronienie uzywania go
w formie "or". Takie rozwiazanie byloby jednak dosc nieeleganckie. Innym
sposobem byloby przeanalizowanie zawartosci form q, r, ... pod katem uzytych
symboli. Klasycznym rozwiazaniem jest wygenerowanie identyfikatora przy pomocy
funkcji "gensym".
: (define-macro (or first . rest)
: (if (eq? rest '())
: first
: (let ((T (gensym)))
: `(let ((,T ,first))
: (if ,T ,T (or ,@rest))))))
Wprawdzie operator "gensym" nie gwarantuje, ze wygenerowany symbol nie byl
dotychczas uzyty w kodzie zrodlowym, ale gwarantuje, ze kazde kolejne jego
wywolanie wygeneruje nowy symbol, zas wygenerowane przezen symbole sa na tyle
dziwne, ze prawdopodobienstwo uzycia ich w praktycznym kodzie jest nikle.
** DEFINICJA FORMY "cond"
Przypomnijmy, ze (pomijajac warunki brzegowe) chcielismy transformowac kod
: (cond (warunek dzialania ...)
: (inny_warunek inne_dzialania ...)
: ...
: (else ostateczne_dzialania ...))
do postaci
: (if warunek
: (begin dzialania ...)
: (cond (inny_warunek inne_dzialania ...)
: ...
: (else ostateczne_dzialania ...)))
Kazda pojedyncza galaz makra "cond" ma postac
: (warunek dzialania ...)
Mozemy zatem zdefiniowac funkcje destrukturyzujace warunki, tak jak robilismy
to przy definicji makra "let":
: (define (cond-condition cond-branch) (car cond-branch))
: (define (cond-actions cond-branch) (cdr cond-branch))
Dzieki temu mozemy latwo napisac definicje naszej formy "cond"
: (define-macro (cond first-branch . remaining-branches)
: (let ((first-condition (cond-condition first-branch))
: (first-actions (cond-actions first-branch)))
: `(if ,(or (eq? first-condition 'else) first-condition)
: (begin ,@first-actions)
: ,@(if (eq? remaining-branches '())
: '()
: `((cond ,@remaining-branches))))))
** DEFINICJA FORMY "while"
Definicja formy "while" nie powinna juz wymagac dodatkowych objasnien:
: (define-macro (while condition . actions)
: (let ((LOOP (gensym)))
: `(call/cc
: (lambda (break)
: (define ,LOOP
: (lambda ()
: (if ,condition
: (begin ,@actions (,LOOP)))))
: (,LOOP)))))
Następne wpisy z tego wątku
- 09.11.14 16:28 JDX
- 09.11.14 17:39 g...@g...com
- 09.11.14 19:04 JDX
- 09.11.14 19:52 firr
- 09.11.14 20:08 g...@g...com
- 09.11.14 20:28 g...@g...com
- 09.11.14 20:35 firr
- 09.11.14 20:52 A.L.
- 09.11.14 21:51 g...@g...com
- 09.11.14 22:05 g...@g...com
- 09.11.14 22:51 Jordan Szubert
- 09.11.14 22:58 g...@g...com
- 09.11.14 23:36 firr
- 10.11.14 01:06 g...@g...com
- 10.11.14 08:53 firr
Najnowsze wątki z tej grupy
- Alg. kompresji LZW
- Popr. 14. Nauka i Praca Programisty C++ w III Rzeczy (pospolitej)
- Arch. Prog. Nieuprzywilejowanych w pełnej wer. na nowej s. WWW energokod.pl
- 7. Raport Totaliztyczny: Sprawa Qt Group wer. 424
- TCL - problem z escape ostatniego \ w nawiasach {}
- Nauka i Praca Programisty C++ w III Rzeczy (pospolitej)
- testy-wyd-sort - Podsumowanie
- Tworzenie Programów Nieuprzywilejowanych Opartych Na Wtyczkach
- Do czego nadaje się QDockWidget z bibl. Qt?
- Bibl. Qt jest sztucznie ograniczona - jest nieprzydatna do celów komercyjnych
- Co sciaga kretynow
- AEiC 2024 - Ada-Europe conference - Deadlines Approaching
- Jakie są dobre zasady programowania programów opartych na wtyczkach?
- sprawdzanie słów kluczowych dot. zła
- Re: W czym sie teraz pisze programy??
Najnowsze wątki
- 2025-02-10 Białystok => iOS Developer (Swift) <=
- 2025-02-10 Mińsk Mazowiecki => Team Lead / Tribe Lead FrontEnd <=
- 2025-02-10 Białystok => System Architect (Java background) <=
- 2025-02-10 Współczesne mierniki zniekształceń nieliniowych THD audio, produkują jakieś?
- 2025-02-10 Szczecin => Senior Field Sales (system ERP) <=
- 2025-02-10 Gliwice => Business Development Manager - Dział Sieci i Bezpieczeńst
- 2025-02-10 Chrzanów => Specjalista ds. public relations <=
- 2025-02-10 Chrzanów => NodeJS Developer <=
- 2025-02-10 Warszawa => JavaScript / Node / Fullstack Developer <=
- 2025-02-10 Gliwice => Ekspert IT (obszar systemów sieciowych) <=
- 2025-02-10 Lublin => Programista Delphi <=
- 2025-02-10 Mińsk Mazowiecki => Area Sales Manager OZE <=
- 2025-02-10 Wrocław => Konsultant wdrożeniowy Comarch XL/Optima (Księgowość i
- 2025-02-10 Dęblin => Node.js / Fullstack Developer <=
- 2025-02-10 Kraków => iOS Developer (Swift experience) <=