среда, 23 сентября 2009 г.

Переменные, локальные относительно контекста

Gnu Emacs поддерживает Buffer-Local Variables, т.е. переменные, которые являются локальными относительного конкретного буфера, а в документации также указано, что в будущем возможно появление переменных, локальных относительно фрэйма или окна. Т.е. вырисовывается достаточно чёткая концепция переменных, локальных относительно некоторого контекста. Это находит применение в Gnu Emacs, но может оказаться полезным и для решения других задач, по крайней мере, я обратил внимание на Buffer-Local Variables уже после того, как сам пришёл к подобной идеи в процессе размышлений над структурой сервера приложений.

Реализация этой идеи на Common Lisp весьма тривиальна:

(defun make-preserve-context ()
(make-hash-table))

(defun context-add-variable (context symbol)
(setf (gethash symbol context)
(symbol-value symbol)))

(defun context-remove-variable (context symbol)
(remhash symbol context))

(defun context-symbol-value (context symbol)
(gethash symbol context))

(defun (setf context-symbol-value) (newval context symbol)
(setf (gethash symbol context)
newval))

(defmacro with-context (context &body body)
`(let ((cntx ,context))
(if cntx
(let ((symbols)
(values))
(iter (for (s v) in-hashtable cntx)
(push s symbols)
(push v values))
(progv symbols values
(unwind-protect
(progn ,@body)
(iter (for s in symbols)
(setf (gethash s cntx)
(symbol-value s))))))
(progn ,@body))))


Теперь можно создать "защищённый контекст", добавить в него несколько ранее объявленных глобальных переменных и изолировать с помощью макроса with-context код, оперирующий этими переменными, от остального мира.

3 комментария:

  1. Вроде бы, похожую вещь делает ContextL (http://common-lisp.net/project/closer/contextl.html).

    ОтветитьУдалить
  2. Не, ContextL делает что-то другое, что я толком даже не понял, хотя и не стремился особо понять, ибо подобные системы меня сразу отталкивают...

    ОтветитьУдалить
  3. Я бы сделал так:

    (defmacro with-context ((context (&rest vars)) &body body)
    (let ((ctx-var (gensym)))
    `(let ((,ctx-var ,context))
    (symbol-macrolet
    ,(loop :for var :in vars
    :collect `(,var (context-symbol-value ,ctx-var ',var)))
    ,@body))))

    Но это дело вкуса. Можно сделать два варианта с разными именами на тот случай, если понадобится лексическое связывание переменных контекста.

    ОтветитьУдалить