Usando a unificação para escrever macros legíveis

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 ( substdo core.unify biblioteca) para preencher as variáveis corporais ?PARMs, ?PRE-CHECKe ?POST-CHECKcom mais estruturas de dados. Especificamente, as estruturas a serem preenchidas são fornecidas em um mapa de ligações subste 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