Em meu código core.contracts, experimentei usar a unificação para ajudar na leitura e na releitura em minhas macros. Freqüentemente, descobri que vou bater em uma parede ao retornar a uma macro que escrevi há muito tempo. Muitas vezes, uma grande quantidade de documentação ajuda, mas eu queria algo mais. Eu acho que encontrei … ou pelo menos o começo de ‘isso’. Observe a seguinte macro:
(defn- build-contract-body
[[args cnstr descr :as V]]
(unify/subst
'(?PARMS
(let [ret ?PRE-CHECK]
?POST-CHECK))
{'?ARGS args
'?F 'f
'?PARMS (vec (list* 'f args))
'?MSG descr
'?PRE-CHECK (build-condition-body {:pre (:pre cnstr)} '(apply ?F ?ARGS) "Pre-condition failure: ")
'?POST-CHECK (build-condition-body {:post (:post cnstr)} 'ret "Post-condition failure: ")}))
Esta macro constrói uma estrutura de dados que corresponde a um corpo de função útil para rastrear falhas de restrição pré e pós-condição. Você verá que a essência da macro é simplesmente:
'(?PARMS
(let [ret ?PRE-CHECK]
?POST-CHECK))
Minha abordagem utiliza unificação ( subst
do core.unify biblioteca) para preencher as variáveis corporais ?PARMs
, ?PRE-CHECK
e ?POST-CHECK
com mais estruturas de dados. Especificamente, as estruturas a serem preenchidas são fornecidas em um mapa de ligações subst
e construídas diretamente ou por meio de outra macro mostrada abaixo:
(defn- build-condition-body
[constraint-map body prefix-msg]
(unify/subst
'(try
((fn []
?CNSTR
?BODY))
(catch AssertionError ae
(throw (AssertionError. (str ?PREFIX ?MSG newline (.getMessage ae))))))
{'?CNSTR constraint-map
'?PREFIX prefix-msg
'?BODY body}))
Usar esse método me permite desenhar efetivamente uma imagem da estrutura de dados que representa o corpo de uma função e preencher os valores necessários por meio da substituição de variável. Preciso explorar isso mais profundamente, mas gosto das descobertas iniciais.
É o RHS de um homem pobre de define-syntax
(talvez).
: F