-
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
- Can you activate BMW 48V 10Ah Li-Ion battery, connecting to CAN-USB laptop interface ?
- We Wrocławiu ruszyła Odra 5, pierwszy w Polsce komputer kwantowy z nadprzewodzącymi kubitami
- Ada-Europe - AEiC 2025 early registration deadline imminent
- John Carmack twierdzi, że gdyby gry były optymalizowane, to wystarczyły by stare kompy
- Ada-Europe Int.Conf. Reliable Software Technologies, AEiC 2025
- Linuks od wer. 6.15 przestanie wspierać procesory 486 i będzie wymagać min. Pentium
- ,,Polski przemysł jest w stanie agonalnym" - podkreślił dobitnie, wskazując na brak zamówień.
- Rewolucja w debugowaniu!!! SI analizuje zrzuty pamięci systemu M$ Windows!!!
- Brednie w wiki - hasło Dehomag
- Perfidne ataki krakerów z KRLD na skrypciarzy JS i Pajton
- Instytut IDEAS może zacząć działać: "Ma to być unikalny w europejskiej skali ośrodek badań nad sztuczną inteligencją."
- Instytut IDEAS może zacząć działać: "Ma to być unikalny w europejskiej skali ośrodek badań nad sztuczną inteligencją."
- Instytut IDEAS może zacząć działać: "Ma to być unikalny w europejskiej skali ośrodek badań nad sztuczną inteligencją."
- U nas propagują modę na SI, a w Chinach naukowcy SI po kolei umierają w wieku 40-50lat
- C++. Podróż Po Języku - komentarz
Najnowsze wątki
- 2025-07-14 granice
- 2025-07-14 Awaria VM?
- 2025-07-14 Gdańsk => Programista Kotlin <=
- 2025-07-14 Warszawa => Junior Rekruter <=
- 2025-07-14 Warszawa => Specjalista rekrutacji IT <=
- 2025-07-14 Wkłady do zniczy...
- 2025-07-14 Warszawa => Specjalista ds. Sprzętu Komputerowego <=
- 2025-07-14 Re: PO chroniło i chroni policyjnych bandziorów [zawiasy za katowanie obywatela (Poznań czerwiec 2012)]
- 2025-07-14 Warszawa => International Freight Forwarder <=
- 2025-07-14 Warszawa => Recruiter 360 <=
- 2025-07-14 Re: Rz?Âd ZAKAZUJE magazyn?Â?w energii ?!! Nowe prawo od 14 lipca to SZOK! ??Â
- 2025-07-14 Warszawa => Sales Assistant <=
- 2025-07-13 Fałszywe alerty
- 2025-07-12 dlaczego gadacie z tym debilem
- 2025-07-13 Unia Europejska przygotowuje nowy podatek