В продолжение анафорических макросов из On Lisp прочел эту же тему в Let Over Lambda Дуга Хойте. Там рассматривается несколько интересных приемов применения анафор, но самое любопытное, что я нашел, – это hotpatch.
Идея заключается в том, что некоторое лямбда-выражение может динамически менять содержимое своей функции. Таким образом, «апдейт» получат одновременно все, кто ее использует.
Идея использования:
(def my (hotpatch [a] (println a)))
Здесь создается анонимная функция, которая печатает свой аргумент; функция связывается с именем my. Теперь к ней можно обращаться так:
(my 11)
Clojure напечатает 11. Теперь заменим динамически содержимое функции.
(my :hotpatch (fn [] (println "Hello world!")))
После такой замены вызов (my) напечатает «Hello world!».
Макрос hotpatch выглядит так.
(defmacro hotpatch [& body]
`(let [my-fn# (ref (fn ~@body))]
(fn [& params#]
(if (and (not (nil? params#))
(> (count params#) 1)
(= :hotpatch (first params#)))
(dosync (ref-set my-fn# (second params#)))
(apply (deref my-fn#) params#))))
А теперь самое интересное. Добавим в макрос hotpatch возможность возвращать текущую функцию.
(defmacro hotpatch [& body]
`(let [my-fn# (ref (fn ~@body))]
(fn [& params#]
(if (and (not (nil? params#))
(= (count params#) 1)
(= (first params#) :fn))
@my-fn#
(if (and (not (nil? params#))
(> (count params#) 1)
(= :hotpatch (first params#)))
(dosync (ref-set my-fn# (second params#)))
(apply (deref my-fn#) params#))))))
Теперь содержимое функции можно вызвать так: ((my :fn))
Вот как с помощью этого макроса реализуется знаменитый паттерн «декоратор»:
(let [old (my :fn)]
(my :hotpatch (fn [] (println "New func") (old))))
По сути и паттерна-то никакого нет, просто функция заменяет свое значение на новое, вызывая в конце старое.
(my)
New func
Hello world!
Таким образом, макрос сильно упрощает использование идиоматичных конструкций.
Комментариев нет:
Отправить комментарий