Les macros Lisp sont-elles utiles?

22

Common Lisp vous permet d'écrire des macros qui effectuent la transformation source que vous souhaitez.

Scheme vous offre un système hygiénique de correspondance de motifs qui vous permet également d'effectuer des transformations. Quelle est l'utilité des macros dans la pratique? Paul Graham a déclaré dans Beating the Averages que:

Le code source de l'éditeur Viaweb était probablement composé d'environ 20-25% de macros.

Quelles sortes de choses les gens finissent-ils par faire avec les macros?

compman
la source
Je pense que cela tombe définitivement dans la bonne subjectivité , j'ai édité votre question pour la mise en forme. Cela pourrait être un doublon, mais je n'ai pas pu en trouver un.
Tim Post
1
Tout ce qui est répétitif et qui ne semble pas correspondre à une fonction, je suppose.
2
Vous pouvez utiliser des macros pour transformer Lisp en n'importe quelle autre langue, avec n'importe quelle syntaxe et toute sémantique: bit.ly/vqqvHU
SK-logic
programmers.stackexchange.com/questions/81202/… mérite d'être examiné ici, mais ce n'est pas un doublon.
David Thornley

Réponses:

15

Jetez un œil à cet article de Matthias Felleisen sur la liste de discussion LL1 en 2002. Il suggère trois utilisations principales des macros:

  1. Sous - langages de données : je peux écrire des expressions simples et créer des listes / tableaux / tableaux imbriqués complexes avec des guillemets, des guillemets, etc. soigneusement habillés de macros.
  2. Constructions de liaison : je peux introduire de nouvelles constructions de liaison avec des macros. Cela m'aide à me débarrasser des lambdas et à rapprocher les choses qui vont ensemble.
  3. Réorganisation de l'évaluation : je peux introduire des constructions qui retardent / retardent l'évaluation des expressions au besoin. Pensez aux boucles, aux nouvelles conditions, au délai / force, etc. [Remarque: dans Haskell ou dans tout langage paresseux, celui-ci est inutile.]
John Clements
la source
18

J'utilise principalement des macros pour ajouter de nouvelles constructions de langage permettant de gagner du temps, ce qui nécessiterait autrement un tas de code standard.

Par exemple, je me suis récemment retrouvé à vouloir un impératif for-loopsimilaire à C ++ / Java. Cependant, étant un langage fonctionnel, Clojure n'est pas venu avec un hors de la boîte. Je l'ai donc implémenté comme une macro:

(defmacro for-loop [[sym init check change :as params] & steps]
  `(loop [~sym ~init value# nil]
     (if ~check
       (let [new-value# (do ~@steps)]
         (recur ~change new-value#))
       value#)))

Et maintenant je peux faire:

 (for-loop [i 0 , (< i 10) , (inc i)] 
   (println i))

Et voilà: une nouvelle construction de langage de compilation à usage général en six lignes de code.

mikera
la source
13

Quelles sortes de choses les gens finissent-ils par faire avec les macros?

Écriture d'extensions de langage ou DSL.

Pour avoir une idée de cela dans les langages de type Lisp, étudiez Racket , qui a plusieurs variantes de langage: Typed Racket, R6RS et Datalog.

Voir aussi le langage Boo, qui vous donne accès au pipeline du compilateur dans le but spécifique de créer des langages spécifiques au domaine via des macros.

Robert Harvey
la source
4

Voici quelques exemples:

Schème:

  • definepour les définitions de fonction. Fondamentalement, cela permet de définir une fonction plus rapidement.
  • let pour créer des variables à portée lexicale.

Clojure:

  • defn, selon ses documents:

    Identique à (def name (fn [params *] exprs *)) ou (def name (fn ([params *] exprs *) +)) avec n'importe quelle chaîne doc ou attrs ajoutée aux métadonnées var

  • for: liste des compréhensions
  • defmacro: ironique?
  • defmethod, defmulti: travailler avec plusieurs méthodes
  • ns

Beaucoup de ces macros facilitent l'écriture de code à un niveau plus abstrait. Je pense que les macros sont similaires, à bien des égards, à la syntaxe non-Lisps.

La bibliothèque de traçage Incanter fournit des macros pour certaines manipulations de données complexes.


la source
4

Les macros sont utiles pour incorporer certains modèles.

Par exemple, Common Lisp ne définit pas la whileboucle mais possède do ce qui peut être utilisé pour la définir.

Voici un exemple de On Lisp .

(defmacro while (test &body body)
  `(do ()
       ((not ,test))
     ,@body))

(let ((a 0))
  (while (< a 10)
    (princ (incf a))))

Cela imprimera "12345678910", et si vous essayez de voir ce qui se passe avec macroexpand-1:

(macroexpand-1 '(while (< a 10) (princ (incf a))))

Cela reviendra:

(DO () ((NOT (< A 10))) (PRINC (INCF A)))

Il s'agit d'une simple macro, mais comme indiqué précédemment, elles sont généralement utilisées pour définir de nouvelles langues ou DSL, mais à partir de cet exemple simple, vous pouvez déjà essayer d'imaginer ce que vous pouvez en faire.

La loopmacro est un bon exemple de ce que les macros peuvent faire.

(loop for i from 0 to 10
      if (and (= (mod i 2) 0) i)
        collect it)
=> (0 2 4 6 8 10)
(loop for i downfrom 10 to 0
      with a = 2
      collect (* a i))
=> (20 18 16 14 12 10 8 6 4 2 0)               

Common Lisp possède un autre type de macros appelé macro de lecture qui peut être utilisée pour modifier la façon dont le lecteur interprète le code, c'est-à-dire que vous pouvez les utiliser pour utiliser # {et #} a des délimiteurs comme # (et #).

Daimrod
la source
3

En voici une que j'utilise pour le débogage (dans Clojure):

user=> (defmacro print-var [varname] `(println ~(name varname) "=" ~varname))
#'user/print-var
=> (def x (reduce * [1 2 3 4 5]))
#'user/x
=> (print-var x)
x = 120
nil

J'ai dû faire face à une table de hachage roulée à la main en C ++, où la getméthode a pris une référence de chaîne non const comme argument, ce qui signifie que je ne peux pas l'appeler avec un littéral. Pour rendre cela plus facile à gérer, j'ai écrit quelque chose comme ceci:

#define LET(name, value, body)  \
    do {                        \
        string name(value);     \
        body;                   \
        assert(name == value);  \
    } while (false)

Bien que quelque chose comme ce problème soit peu susceptible de se produire dans lisp, je trouve particulièrement agréable que vous puissiez avoir des macros qui n'évaluent pas leurs arguments deux fois, par exemple en introduisant une vraie liaison de let. (Admis, ici j'aurais pu m'en sortir).

J'ai également recours au hack horriblement laid de l'emballage de choses dans un do ... while (false)tel que vous pouvez l'utiliser dans la partie alors d'un si et que la partie autre fonctionne toujours comme prévu. Vous n'avez pas besoin de cela dans lisp, qui est une fonction de macros opérant sur des arbres de syntaxe plutôt que des chaînes (ou des séquences de jetons, je pense, dans le cas de C et C ++) qui subissent ensuite une analyse.

Il existe quelques macros de threading intégrées qui peuvent être utilisées pour réorganiser votre code de manière à ce qu'il soit plus clair («threading» comme dans «semer votre code ensemble», pas le parallélisme). Par exemple:

(->> (range 6) (filter even?) (map inc) (reduce *))

Il prend la première forme, (range 6)et en fait le dernier argument de la forme suivante (filter even?), qui à son tour devient le dernier argument de la forme suivante et ainsi de suite, de sorte que ce qui précède est réécrit en

(reduce * (map inc (filter even? (range 6))))

Je pense que le premier se lit beaucoup plus clairement: "prenez ces données, faites-y ceci, puis faites cela, puis faites l'autre et nous avons terminé", mais c'est subjectif; ce qui est objectivement vrai, c'est que vous lisez les opérations dans l'ordre où elles sont effectuées (en ignorant la paresse).

Il existe également une variante qui insère le formulaire précédent en tant que premier (plutôt que dernier) argument. Un cas d'utilisation est l'arithmétique:

(-> 17 (- 2) (/ 3))

Lit "prendre 17, soustraire 2 et diviser par 3".

En parlant d'arithmétique, vous pouvez écrire une macro qui analyse la notation infixée, de sorte que vous pourriez dire par exemple (infix (17 - 2) / 3)et qu'elle cracherait ce (/ (- 17 2) 3)qui a l'inconvénient d'être moins lisible et l'avantage d'être une expression lisp valide. C'est la partie sous-langage DSL / données.

Jonas Kölker
la source
1
L'application de fonctions a beaucoup plus de sens pour moi que le filetage, mais c'est certainement une question d'habitude. Bonne réponse.
coredump