понедельник, 31 января 2011 г.

Hotpatch и декораторы


В продолжение анафорических макросов из 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!

Таким образом, макрос сильно упрощает использование идиоматичных конструкций.

Комментариев нет:

Отправить комментарий