Selon cet article, la ligne suivante de code Lisp imprime "Hello world" sur la sortie standard.
(format t "hello, world")
Le lisp, qui est un langage homoiconique , peut traiter le code comme des données de cette manière:
Imaginez maintenant que nous avons écrit la macro suivante:
(defmacro backwards (expr) (reverse expr))
en arrière est le nom de la macro, qui prend une expression (représentée sous forme de liste) et l'inverse. Voici à nouveau "Bonjour, monde", cette fois en utilisant la macro:
(backwards ("hello, world" t format))
Lorsque le compilateur Lisp voit cette ligne de code, il examine le premier atome de la liste (
backwards
) et remarque qu'il nomme une macro. Il transmet la liste non évaluée("hello, world" t format)
à la macro, qui réorganise la liste en(format t "hello, world")
. La liste résultante remplace l'expression de macro et c'est ce qui sera évalué au moment de l'exécution. L'environnement Lisp verra que son premier atome (format
) est une fonction et l'évaluera en lui passant le reste des arguments.
En Lisp, cette tâche est facile (corrigez-moi si je me trompe) car le code est implémenté sous forme de liste ( expressions s ?).
Jetez maintenant un œil à cet extrait de code OCaml (qui n'est pas un langage homoiconique):
let print () =
let message = "Hello world" in
print_endline message
;;
Imaginez que vous souhaitiez ajouter une homoiconicité à OCaml, qui utilise une syntaxe beaucoup plus complexe que Lisp. Comment feriez-vous cela? Le langage doit-il avoir une syntaxe particulièrement simple pour atteindre l'homoiconicité?
EDIT : à partir de ce sujet, j'ai trouvé un autre moyen d'atteindre l'homoiconicité qui est différent de Lisp: celui implémenté dans le langage io . Il peut répondre partiellement à cette question.
Ici, commençons par un simple bloc:
Io> plus := block(a, b, a + b) ==> method(a, b, a + b ) Io> plus call(2, 3) ==> 5
D'accord, donc le bloc fonctionne. Le bloc plus a ajouté deux nombres.
Faisons maintenant une introspection sur ce petit bonhomme.
Io> plus argumentNames ==> list("a", "b") Io> plus code ==> block(a, b, a +(b)) Io> plus message name ==> a Io> plus message next ==> +(b) Io> plus message next name ==> +
Moule chaud et froid. Non seulement vous pouvez obtenir les noms des paramètres de bloc. Et non seulement vous pouvez obtenir une chaîne du code source complet du bloc. Vous pouvez vous faufiler dans le code et parcourir les messages à l'intérieur. Et le plus étonnant de tous: c'est terriblement facile et naturel. Fidèle à la quête d'Io. Le miroir de Ruby ne peut rien voir de tout ça.
Mais, whoa whoa, hé maintenant, ne touchez pas ce cadran.
Io> plus message next setName("-") ==> -(b) Io> plus ==> method(a, b, a - b ) Io> plus call(2, 3) ==> -1
Réponses:
Vous pouvez rendre n'importe quelle langue homoiconique. Essentiellement, vous faites cela en «reflétant» le langage (ce qui signifie que pour tout constructeur de langage, vous ajoutez une représentation correspondante de ce constructeur en tant que données, pensez AST). Vous devez également ajouter quelques opérations supplémentaires telles que la citation et la non-cotation. C'est plus ou moins ça.
Lisp l'a fait très tôt en raison de sa syntaxe facile, mais la famille de langues MetaML de W. Taha a montré qu'il était possible de le faire pour n'importe quelle langue.
L'ensemble du processus est décrit dans Modélisation de la méta-programmation générative homogène . Une introduction plus légère au même matériau est ici .
la source
Le compilateur Ocaml est écrit en Ocaml lui-même, il y a donc certainement un moyen de manipuler les AST Ocaml dans Ocaml.
On pourrait imaginer ajouter un type intégré
ocaml_syntax
au langage et avoir unedefmacro
fonction intégrée, qui prend une entrée de type, disonsMaintenant, quel est le type de
defmacro
? Eh bien, cela dépend vraiment de l'entrée, car même sif
c'est la fonction d'identité, le type du morceau de code résultant dépend du morceau de syntaxe transmis.Ce problème ne se pose pas dans lisp, car le langage est typé dynamiquement et aucun type n'a besoin d'être attribué à la macro elle-même au moment de la compilation. Une solution serait d’avoir
ce qui permettrait à la macro d'être utilisée dans n'importe quel contexte. Mais ce n'est pas sûr, bien sûr, cela permettrait
bool
à a d'être utilisé à la place de astring
, faisant planter le programme au moment de l'exécution.La seule solution de principe dans un langage de type statique serait d'avoir des types dépendants dans lesquels le type de résultat
defmacro
dépendrait de l'entrée. Les choses deviennent assez compliquées à ce stade cependant, et je commencerais par vous signaler la belle dissertation de David Raymond Christiansen.En conclusion: avoir une syntaxe compliquée n'est pas un problème, car il existe de nombreuses façons de représenter la syntaxe à l'intérieur du langage, et éventuellement d'utiliser la méta-programmation comme une
quote
opération pour incorporer la syntaxe "simple" dans l'interneocaml_syntax
.Le problème est de bien faire ce type, en particulier d'avoir un mécanisme de macro d'exécution qui ne permet pas les erreurs de type.
Il est bien sûr possible d' avoir un mécanisme de compilation pour les macros dans un langage comme Ocaml, voir par exemple MetaOcaml .
Peut-être aussi utile: Jane street sur la méta-programmation dans Ocaml
la source
Par exemple, considérons F # (basé sur OCaml). F # n'est pas entièrement homoiconique, mais prend en charge l'obtention du code d'une fonction en tant qu'AST dans certaines circonstances.
En F #, votre
print
serait représenté comme unExpr
qui est imprimé comme:Pour mieux mettre en valeur la structure, voici une autre façon de créer la même chose
Expr
:la source
eval(<string>)
fonction? ( Selon de nombreuses ressources, avoir une fonction eval est différent d'avoir une homoiconicité - est-ce la raison pour laquelle vous avez dit que F # n'est pas entièrement homoiconique?)print
avec l'[<ReflectedDefinition>]
attribut.)