• [^] # Re: C'est bien dommage

    Posté par . En réponse au journal C++17 est sur les rails. Évalué à 2.

    Pour le passage par valeur/référence, les paramètres des fonctions lisp sont passés par valeur, l'essentiel des valeurs étant des références (comme en java, si ça te dit quelque chose : dans les variables on a en général des références à des objets).

    Les macros reçoivent en paramètre des expressions Lisp (du code Lisp), sous forme non de texte mais d'informations structurées (l'AST, qui est un truc tout simple constitué de listes imbriquées).

    Je vais prendre un exemple et le dérouler, ce sera plus simple...

    En Lisp, if est un opérateur spécial. Il s'agit d'un des quelques opérateurs existant dans le langage, qui ne peut pas être représenté par une fonction mais pas non plus par une macro parce qu'une macro ne fait que générer du code Lisp de plus bas niveau. Donc il faut bien une conditionnelle "élémentaire" pour construire les autres. Et par exemple cond, l'équivalent d'un if/elseif/elseif/else dans d'autres langages, est une macro qui se base sur if (qui génère du code contenant des if, en remplacement du cond).

    Les if s'utilisent comme ça en général (condition puis l'expression à évaluer si la condition est réalisée puis celle à évaluer sinon) :

    (if (= x 33)
     (print "réussi")
     (print "raté"))

    Les cond s'utilisent comme ça (t signifie "vrai" : la troisième condition est toujours vérifiée ; sachant que seule la première expression vérifiée conduit à évaluer l'expression correspondante) :

    (cond
     ((= x 33)
     (print "réussi"))
     ((= x 32)
     (print "presque"))
     (t
     (print "raté")))

    Il se trouve que if est un opérateur spécial et cond une macro, qui se base sur if. Pour simplifier mon exemple je vais imaginer l'inverse : cond serait un opérateur spécial compris par le compilateur et qui ne peut pas être écrit sous forme de fonction ou de macro, il n'existerait pas de macro if et je veux créer cette dernière.

    Le but est donc d'écrire une macro if qui permettra ensuite d'écrire ce code (1) :

    (if (= x 33)
     (print "réussi")
     (print "raté"))

    et qui le transforme en ce code, qui ne contient plus que cond, supposé être connu par le compilateur (2) :

    (cond
     ((= x 33)
     (print "réussi"))
     (t
     (print "raté")))

    On veut donc écrire une macro qui prend en paramètre 3 expressions Lisp : dans mon exemple ce serait (= x 33) (une liste contenant le symbole =, le symbole x et le nombre 33), (print "réussi") (une liste contenant le symbole print et la chaîne "réussi"), (print "raté") (une liste contenant le symbole print et la chaîne "raté").

    Les paramètres sont des listes représentant l'AST de ces 3 expressions. L'analyse syntaxique est déjà passée par là, la macro ne reçoit pas en paramètre le texte des expressions mais bien les expressions structurées en mémoire. C'est à dire que la macro peut vérifier si une des expressions reçues en paramètre est une liste, un nombre, ... et si c'est une liste demander le nombre d'éléments, le premier élément, ... : elle peut manipuler les expressions simplement, comme n'importe quelle liste contenant des données (=> le code est passé sous forme de données).

    Le but de la macro est de renvoyer une expression Lisp sous forme d'AST (des listes imbriquées) représentant le code généré. Dans ce code (mon exemple (2) plus haut), on voit bien les listes imbriquées, chaque paire de parenthèses délimitant une liste : le code résultat est constitué de listes, il va être simple de le construire en imbriquant des listes.

    Un dernier mot sur la fonction list : il s'agit simplement d'une fonction permettant de créer une liste. (list 1 2 "coucou") crée une liste contenant 3 éléments (honteusement pas du même type ;) ).

    La version moche de la macro, qui laisse bien apparaître la manipulation des listes et montre que les macros sont des bouts de code Lisp qui génèrent du code représenté par des listes imbriquées :

    (defmacro if (condition expr1 expr2)
     (list 'cond
     (list condition
     expr1)
     (list t
     expr2)))

    Le code crée une liste contenant le symbole cond et deux sous-listes. La première sous-liste contient deux éléments : la condition (dans mon exemple (= x 33)) et la première expression (dans mon exemple (print "réussi")).

    Une version plus simple de la macro (l'opérateur "`" permet de construire une liste, l'opérateur "," d'insérer dedans quelque chose à évaluer ; ici simplement la valeur des paramètres : la liste ainsi construite contient le symbole cond mais la valeur du paramètre condition) :

    (defmacro if (condition expr1 expr2)
     `(cond
     (,condition
     ,expr1)
     (t
     ,expr2)))

    Est-ce que tu vois comment les macros fonctionnent ? Elles prennent en paramètre des expressions Lisp qui sont des AST sous forme de listes imbriquées et renvoient un AST sous forme de listes imbriquées, le tout au moment de la compilation. L'AST généré remplaçant le code dont la macro est la racine (dans mon exemple le code (1) est remplacé par le code (2) : le compilateur voit une expression commençant par if, appelle la macro if, remplace cette expression par le résultat de l'exécution de la macro).

    Il n'y a pas de notion d'évaluation paresseuse ou non : la macro génère simplement un bout de code Lisp destiné à remplacer l'ancien. Et ce bout de code n'est rien d'autre que du code Lisp normal. De manière générale il n'y a pas d'évaluation paresseuse en Common Lisp. Par contre effectivement il faut faire attention en écrivant les macros : ici c'est très simple mais il faut éviter d'insérer expr1 à plusieurs endroits dans le code généré, sinon l'expression sera exécutée plusieurs fois. Dans ce cas il faut générer du code qui évalue cette expression et place le résultat dans une variable puis utilise cette variable. C'est un problème plus sournois dans les macros parce qu'on a vite fait d'écrire deux fois expr1 sans s'en rendre compte et d'insérer deux fois une expression dans le code généré, ce qui peut mener à des surprises au moment de l'exécution, mais ce n'est rien d'autre que l'évaluation normale de Common Lisp d'un code dans lequel on aurait recopié deux fois la même expression.

    Par contre la macro est évaluée au moment de la compilation, donc une seule fois.

    Il reste juste à noter que cette macro est assez simple mais qu'elle pourrait faire des calculs complexes, parcourir les expressions qu'on lui passe en paramètre (il s'agit de simples listes), faire n'importe quoi pour générer le code Lisp destiné à la remplacer. Il s'agit simplement de code Lisp quelconque manipulant des listes (parcours, construction, ...).

    Est-ce que c'est plus clair ?

    (Et comment vous faites pour la coloration syntaxique de Common Lisp ? J'ai mis clojure parce que je n'arrive pas à la faire marcher avec "Common Lisp" ; ça marche bizarrement d'ailleurs, parfois j'écris n'importe quoi comme langage, ou même je le supprime, et ça continue à fonctionner : j'ai supprimé le langage sur le premier extrait de code et il est coloré quand même).