2010-11-26 24 views
2

OK, je souhaite écrire une macro Clojure qui définit une struct-map et permet à l'appelant de spécifier des types pour chaque champ.Macro de Clojure - Définir une liaison dont le nom est composé à partir d'un argument

La signature ressemblera à ceci:

(defmodel category :id Integer :name String) 

Ce que cela fait est qu'il crée une catégorie appelée struct-map, et aussi créer un *category-meta* de liaison, qui est une carte {:id Integer :name String}

Voici ma macro pour atteindre cela:

(defmacro defmodel [name & field-spec] 
    `(let [fields# (take-nth 2 [email protected])] 
     (defstruct ~name fields#) 
     (def *~name-meta* (reduce #(assoc %1 (first %2) (last %2))) (partition 2 [email protected])))) 

Cependant, le problème est, je ne peux pas définir une liaison dont le nom est composé de f un autre nom. Fondamentalement, (def *~name-meta* ...) ne fonctionne pas.

Comment puis-je y parvenir?

Merci.

Répondre

3

(mis à jour avec une version déboguée de la macro à partir du texte de la question.)

Cela devrait fonctionner comme indiqué:

(defmacro defmodel [name & field-spec] 
    `(do (defstruct ~name [email protected](take-nth 2 field-spec)) 
     (def ~(symbol (str "*" name "-meta*")) 
     (reduce #(assoc %1 (first %2) (last %2)) 
       {} 
       (partition 2 '~field-spec))))) 

La réponse à la question principale est d'utiliser ~(symbol (str "*" name "-meta*")) en place de *~name-meta*. ~ désintègre l'expression suivante sous une forme de syntaxe, en injectant sa valeur de retour dans l'emplacement approprié de la structure de liste donnée.

D'autres modifications ont été nécessaires - en particulier, defstruct exige que les clés lui soient fournies en tant qu'arguments séparés, plutôt qu'un seul seq (ou le nom d'une variable contenant un tel seq), reduce a besoin de la graine explicite Par ailleurs, sauf si vous avez besoin de vous en tenir à Clojure 1.1, vous pouvez utiliser le defrecord de 1.2 plutôt que le defstruct - en fait, ce dernier est obsolète dans 1.2.

+0

Merci pour votre réponse claire. J'ai regardé defrecord avant, mais la principale lacune est que vous devez fournir toutes les valeurs de champ au moment de l'initialisation. par exemple, (catégorie defrecord [^ String id^String name]), et je dois l'appeler comme ceci '(pile de catégorie 1" ")'. Je ne peux pas faire quelque chose comme (category. {: Id 1: nom "Stack"}), ou (catégorie 1), c'est-à-dire, autoriser les paramètres par défaut. On dirait que le support d'enregistrement est encore provisoire et peut changer à tout moment. N'hésitez pas à sonner et dites-moi tout ce que je n'ai pas connu sur les dossiers jusqu'à présent :) – EnToutCas

+0

Je considérerais écrire un entrepreneur qui fait ce que vous voulez. les enregistrements sont plus rapides et les protocoles peuvent être implémentés. Quelques enregistrements plus puissants peuvent être trouvés ici https://github.com/david-mcneil/defrecord2 – nickik

+0

@EnToutCas: Je n'appellerais pas le support d'enregistrement une tentative - les cartes de struct vraiment * ont * été dépréciées après tout, avec des enregistrements étant le remplacement suggéré. (C'est sans parler de la probabilité d'une convention sur les fonctions de l'usine qui pourrait éventuellement être intégrée à la bibliothèque de base à l'avenir.) En ce qui concerne vos problèmes spécifiques, je suis fondamentalement d'accord avec nickik. (En fait, je trouve utile d'écrire des fonctions d'usine pour presque tous mes enregistrements, ne serait-ce que pour pouvoir les "requérir" plus tard sans "importer" tous les enregistrements.) @nickik: Merci pour le lien! –