6 Scheme en Scheme
Où lectrices et lecteurs découvriront, abasourdis,
l'interprète Scheme écrit en Scheme lui-même et ce qui s'ensuivra.
Voici la définition d'un interprète Scheme en Scheme:
(define (evaluate e env)
(if (atom? e) ;
(cond ((symbol? e) (lookup e env))
((or (number? e) (string? e) (char? e) (boolean? e))
e )
(else (wrong "Cannot evaluate" e)) )
(case (car e)
;;
((quote) (cadr e))
((if) (if (evaluate (cadr e) env)
(evaluate (caddr e) env)
(evaluate (cadddr e) env) ))
;;
((begin) (eprogn (cdr e) env))
((set!) (update! (cadr e) env (evaluate (caddr e) env)))
((lambda) (make-function (cadr e) (cddr e) env))
(else (invoke (evaluate (car e) env)
(evlis (cdr e) env) )) ) ) )
Deux itérateurs évaluant leur premier argument:
;;;
(define (evlis exps env)
(if (pair? exps)
(cons (evaluate (car exps) env)
(evlis (cdr exps) env) )
'() ) )
(define (eprogn exps env)
(if (pair? exps)
(if (pair? (cdr exps))
(begin (evaluate (car exps) env)
(eprogn (cdr exps) env) )
(evaluate (car exps) env) )
'() ) )
La représentation des fonctions:
;;;
(define (make-function variables body env)
(lambda (values)
(eprogn body (extend env variables values)) ) )
(define (invoke fn args)
(if (procedure? fn)
(fn args)
(wrong "Not a function" fn) ) )
La représentation des environnements:
;;;
;;;
(define (lookup id env)
(if (pair? env)
(if (eq? (caar env) id)
(cdar env)
(lookup id (cdr env)) )
(wrong "No such binding" id) ) )
(define (update! id env value)
(if (pair? env)
(if (eq? (caar env) id)
(set-cdr! (car env) value)
(update! id (cdr env) value) )
(wrong "No such binding" id) ) )
(define (extend env names values)
(cond ((pair? names)
(if (pair? values)
(cons (cons (car names) (car values))
(extend env (cdr names) (cdr values)) )
(wrong "Too less values") ) )
((null? names)
(if (null? values)
env
(wrong "Too much values") ) )
((symbol? names) (cons (cons names values) env)) ) )
L'environnement initial:
(define env.init '())
;;;
(define env.global env.init)
Les définisseurs de liaisons prédéfinies:
(define-macro (definitial name value)
`(begin (set! env.global (cons (cons ',name ,value) env.global))
',name ) )
(define-macro (defprimitive name value arity)
`(definitial ,name
(lambda (values)
(if (= (length values) ,arity)
(apply ,value values)
(wrong "Incorrect arity"
(list ',name values) ) ) ) ) )
(define the-false-value #f)
(define-macro (defpredicate name value arity)
`(definitial ,name
(lambda (values)
(if (= ,arity (length values))
(or (apply ,value values) the-false-value)
(wrong "Incorrect arity"
(list ',name values) ) ) ) ) )
Quelques fonctions prédéfinies:
(defprimitive car car 1)
(defprimitive cons cons 2)
Lancement de l'interprète:
;;;
(define (toplevel)
(display (evaluate (read) env.global))
(toplevel) )
Exercice 51 : Modifier le lancement de l'interprète pour ajouter une bannière,
une invite et détecter la fin de fichier.
Solution de l'exercice 51 :
(define (scheme)
(display "Bienvenue !")
(newline)
(let toplevel ()
(display "?? ")
(let ((e (read)))
(if (eof-object? e)
(begin (display ";;;
(newline) )
(let ((r (evaluate e env.global)))
(display "== ")
(display r)
(newline)
(toplevel) ) ) ) ) )
Exercice 52 : Définir les fonctions list et apply pour cet
interprète. On pourra se limitera à un apply binaire.
Solution de l'exercice 52 : Voici un apply n-aire. Comme list est aussi une fonction
n-aire, on définira auparavant un définisseur de fonctions n-aires.
(defnaryprimitive list
(lambda values values)
0 )
(define-macro (defnaryprimitive name value arity)
`(definitial ,name
(lambda (values)
(if (>= (length values) ,arity)
(apply ,value values)
(wrong "Incorrect arity"
(list ',name values) ) ) ) ) )
(defnaryprimitive apply
(lambda values
(define (listify arguments)
(if (pair? (cdr arguments))
(cons (car arguments) (listify (cdr arguments)))
(car arguments) ) )
(invoke (car values) (listify (cdr values))) )
2 )
Exercice 53 : Changer la représentation des fonctions afin que la valeur de
cons dans le Scheme interprété soit la valeur de cons dans
le Scheme sous-jacent. Réécrire alors list et apply.
Solution de l'exercice 53 :
;;;
(define (make-function variables body env)
(lambda values
(eprogn body (extend env variables values)) ) )
(define (invoke fn args)
(if (procedure? fn)
(apply fn args)
(wrong "Not a function" fn) ) )
;;;
(define-macro (defprimitive name value arity)
`(definitial ,name ,value) )
(defnaryprimitive list list 0)
(defnaryprimitive apply apply 2)
Exercice 54 : Modifier l'affectation afin qu'elle crée les variables si
inexistantes.
Solution de l'exercice 54 : Les variables sont créées ici au niveau global. Il suffira de
remplacer update! par update!!.
;;;
(define (update!! id env value)
(if (pair? env)
(if (eq? (caar env) id)
(begin (set-cdr! (car env) value)
value )
(update!! id (cdr env) value) )
(begin (set-cdr! (last-pair env.global)
(list (cons id value)) )
value ) ) )
Exercice 55 : Compte-tenu de l'interprète donné ci-avant, ajouter le code
nécessaire permettant de tracer l'évaluation des expressions qui
lui sont soumises.
Solution de l'exercice 55 : On ajoutera par exemple en tête de la fonction evaluate, la
verrue suivante:
(define (evaluate e env)
(display `(evaluation de ,e))(newline)
(let ((resultat code original))
(display `(le resultat est ,resultat))(newline)
resultat ) )
Exercice 56 : Au lieu d'imprimer bestialement, procurer à l'utilisateur la
possibilité d'indiquer les seuls appels qu'il souhaite voir (cf.
trace) ou bien lui offrir une nouvelle boucle d'interaction locale
lui permettant d'inspecter l'environnement.
Solution de l'exercice 56 : On écrira par exemple:
(define (evaluate e env)
(display `(ATTENTION je vais evaluer ,e))(newline)
(toplevel env) ;
(let* ((resultat code original)
(nom 'resultat)
(env (extend env (list nom) (list resultat))) )
(display `(ATTENTION le ,nom est ,resultat))(newline)
(toplevel env) ;
(lookup nom) ) )
Exercice 57 : Modifier l'interprète précédent pour qu'un nombre en
position fonctionnelle soit analogue à un sélecteur de liste. Un
nombre positif correspondra à cadnr tandis qu'un nombre
négatif correspondra à cd-nr. Par exemple
? (NOUVEL-EVAL '(2 '(A B C D)))
= C
? (NOUVEL-EVAL '(-2 '(A B C D)))
= (C D)
Solution de l'exercice 57 : On raffinera la fonction invoke pour accepter les nombres.
;;;
(define (invoke fn args)
(cond ((procedure? fn)
(apply fn args) )
((integer? fn)
(if (>= fn 0) (list-ref (car args) fn)
(list-tail (car args) (- fn)) ) )
(else (wrong "Cannot invoke" fn)) ) )
Exercice 58 : Modifier l'interprète précédent afin d'autoriser une écriture
infixe des fonctions. Par exemple:
? (AUTRE-EVAL '(1 + (3 * 4)))
= 13
Solution de l'exercice 58 : On raffinera encore invoke pour accepter cette syntaxe.
;;;
(define (invoke fn args)
(cond ((procedure? fn)
(apply fn args) )
((and (pair? args) (procedure? (car args)))
(invoke (car args) (cons fn (cdr args))) )
(else (wrong "Cannot invoke" fn)) ) )
Macro-expansion
Exercice 59 : Ajouter une phase de macroexpansion à l'interprète. On commencera
par écrire une fonction prenant une expression, l'arpentant et
remplaçant toute expression, dont le car est un symbole
connu, par le résultat de l'expanseur associé à ce
symbole. Ensuite on insérera ce macro-expanseur dans la boucle
d'interaction et on créera une macro prédéfinie de définition
de macros. Voici un exemple:
? (define-macro (foo x)
(list 'quote (list x x)) )
= foo
? (foo 3)
= (3 3)
Solution de l'exercice 59 : Le modèle qui suit ne permet pas de macro locales. Il ne procure
qu'un unique définisseur de macro define-macro. Celui-ci
communique avec le macro-expanseur à l'aide d'une variable
partagée. On notera l'usage d'evaluate pour convertir, à la
volée, le texte de l'expanseur en une fonction invoquable. On notera
aussi la reconnaissance de la citation pour éviter d'expanser son
paramètre.
Voici tout d'abord l'expanseur:
(define (expand-expression e)
(if (pair? e)
(case (car e)
((quote) e)
((lambda) `(lambda ,(cadr e) . ,(expand-expressions (cddr e))))
(else
(let ((expander (assoc (car e) macro-env)))
(if (pair? expander)
;;
(let ((ee (invoke (cdr expander) (list e))))
(expand ee) )
(expand-expressions e) ) ) ) )
e ) )
(define (expand-expressions e*)
(if (pair? e*)
(cons (expand-expression (car e*))
(expand-expressions (cdr e*)) )
e* ) )
(define macro-env
(list (cons 'define-macro macro-definer)) )
Voici la macro prédéfinie de création de macros. La variable
macro-env est une variable interne a l'implantation. On pourrait
aussi la rendre visible du Scheme interprété.
(define (macro-definer e)
(let* ((call (cadr e))
(body (cddr e))
(name (car call))
(vars (cdr call)) )
;;
(set! macro-env
;;
(cons (cons name (evaluate `(lambda (e)
(apply (lambda ,vars . ,body)
(cdr e) ) )
env.predefined ) )
macro-env ) )
`(quote ,name) ) )
Puis une façon d'intégrer le macro-expanseur à
la boucle d'évaluation. On utilisera une variable du Scheme
interprété pour contenir l'expanseur courant.
(define (scheme2)
(display "Bienvenue !")
(newline)
(let toplevel ()
(display "?? ")
(let ((e (expand-program (read))))
(if (eof-object? e)
(begin (display ";;;
(newline) )
(let ((r (evaluate e env.global)))
(display "== ")
(display r)
(newline)
(toplevel) ) ) ) ) )
(set! env.predefined
(cons (cons 'expand expand-expression)
;;
(append env.global '()) ) )
(define (expand-program e)
(evaluate `(expand ',e) env.predefined) )