eGospodarka.pl
eGospodarka.pl poleca

eGospodarka.plGrupypl.comp.programmingMakra w jezyku SchemeRe: Makra w jezyku Scheme
  • X-Received: by 10.140.105.118 with SMTP id b109mr1414qgf.31.1415540335391; Sun, 09
    Nov 2014 05:38:55 -0800 (PST)
    X-Received: by 10.140.105.118 with SMTP id b109mr1414qgf.31.1415540335391; Sun, 09
    Nov 2014 05:38:55 -0800 (PST)
    Path: news-archive.icm.edu.pl!news.icm.edu.pl!newsfeed.pionier.net.pl!news.glorb.com!
    h15no954710igd.0!news-out.google.com!u1ni1qah.0!nntp.google.com!i13no329228qae.
    0!postnews.google.com!glegroupsg2000goo.googlegroups.com!not-for-mail
    Newsgroups: pl.comp.programming
    Date: Sun, 9 Nov 2014 05:38:54 -0800 (PST)
    In-Reply-To: <6...@g...com>
    Complaints-To: g...@g...com
    Injection-Info: glegroupsg2000goo.googlegroups.com; posting-host=46.186.78.6;
    posting-account=f7iIKQoAAAAkDKpUafc-4IXhmRAzdB5r
    NNTP-Posting-Host: 46.186.78.6
    References: <c...@g...com>
    <f...@g...com>
    <6...@g...com>
    User-Agent: G2/1.0
    MIME-Version: 1.0
    Message-ID: <4...@g...com>
    Subject: Re: Makra w jezyku Scheme
    From: g...@g...com
    Injection-Date: Sun, 09 Nov 2014 13:38:55 +0000
    Content-Type: text/plain; charset=ISO-8859-1
    Xref: news-archive.icm.edu.pl pl.comp.programming:206867
    [ ukryj 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)))))

Podziel się

Poleć ten post znajomemu poleć

Wydrukuj ten post drukuj


Następne wpisy z tego wątku

Najnowsze wątki z tej grupy


Najnowsze wątki

Szukaj w grupach

Eksperci egospodarka.pl

1 1 1

Wpisz nazwę miasta, dla którego chcesz znaleźć jednostkę ZUS.

Wzory dokumentów

Bezpłatne wzory dokumentów i formularzy.
Wyszukaj i pobierz za darmo: