Я пишу код на Лиспе на Haskell(на GitHub ), чтобы больше узнать об обоих языках.
Новейшей функцией, которую я добавляю, являются макросы. Не гигиенические макросы или что-то необычное -, а простые преобразования ванильного кода. Моя первоначальная реализация имела отдельную макросреду, отличную от среды, в которой живут все остальные значения. Между функциями read
и eval
я вставил еще одну функцию, macroExpand
,который обходил дерево кода и выполнял соответствующие преобразования каждый раз, когда находил ключевое слово в среде макроса, прежде чем окончательная форма была передана в eval
для оценки. Приятным преимуществом этого было то, что макросы имели то же внутреннее представление, что и другие функции, что уменьшало некоторое дублирование кода.
Наличие двух сред казалось неуклюжим, и меня раздражало, что если я хотел загрузить файл, eval
должен был иметь доступ к среде макросов, если файл содержал определения макросов. Поэтому я решил ввести тип макроса, хранить макросы в той же среде, что и функции и переменные, и включить фазу раскрытия макроса в eval
. Сначала я немного не знал, как это сделать, пока не понял, что могу просто написать этот код:
eval env (List (function : args)) = do
func <- eval env function
case func of
(Macro {}) -> apply func args >>= eval env
_ -> mapM (eval env) args >>= apply func
Он работает следующим образом:
Как будто макросы точно такие же, как функции, за исключением того, что порядок eval/apply изменен.
Это точное описание макросов? Я упускаю что-то важное, реализуя макросы таким образом? Если ответы «да» и «нет», то почему я никогда раньше не видел такого объяснения макросов?