Но вот я стал изучать Common Lisp и роясь в исходников многих библиотек обнаружил, что lisp-разработчики совершенно не стесняются использовать глобальные переменные. Что это? Код был написан профессионально и качественно, но широкое использование глобальных переменных меня просто шокировало. Разве они (разработчики) не знают что это очень опасно? К тому же, конкретно библиотека cl-pdf вызывала у меня серьезные опасения за корректностью своей работы в условиях многопоточности (использовалась в рамках веб-сервера hunchentoot). Поскольку система, всё же, работала нормально, то видимо была какая-то тонкость, которую я не понимал. Посему, просто отложил этот факт в памяти и вернулся к работе.
Первым шагом к пониманию стало прочтение в SICP описания "модели вычислений с окружением". Знания получил, но понимания тогда ещё не обрёл :-)
Наступлению прозрения, как это часто бывает, способствовало несчастье :-) При работе с cl-pdf в sbcl при текущей локали ru_RU.UTF-8 у меня неверно обрабатывался русский текст и получались всякие невменяемые закорючки. Исследование выявило проблему не в самой cl-pdf, а в chunga (при работе под sbcl). Проблему можно было решить либо небольшим патчем (буквально, переделать одну строку), что мне не очень нравилось, либо в моём коде за счёт временного изменения переменной sb-impl::*default-external-format*, которую следовало временно установить, например, в :latin-1. Первоначально я написал примерно следующее:
(let ((old-external-format sb-impl::*default-external-format*))Необходимого эффекта я добился, но код, конечно, совершенно ужасен. Во-первых, не обрабатываются возможные исключения, в результате чего, измененная глобальная переменная может так и не восстановить свое прежнее значение (конечно, можно добавить обработку исключений, но смотреть будет еще страшнее). Во-вторых, система изменяются глобально, что в условиях многопоточности чревато большими проблемами (наступление которых - лишь вопрос времени). Короче, весьма характерный пример проблем с глобальными переменными. И тут я понял:
(setf sb-impl::*default-external-format* :latin-1)
(|Здесь код по выводу сгенерённого pdf-документа в поток|)
(setf sb-impl::*default-external-format* old-external-format))
(let ((sb-impl::*default-external-format* :latin-1))Это работает! Здесь значение глобальной переменной sb-impl::*default-external-format* связывается с новым значение, которое действует только внутри блока let и совершенно не затрагивает остальную систему. И это потрясающе! Такая возможность решает изрядную долю проблем, связанных с использованием глобальных переменных и позволяет делать код проще без потери гибкости за счёт возможности передавать аргументы не через формальные аргументы функций, а через "окружение".
(|Здесь код по выводу сгенерённого pdf-документа в поток|))
P.S. lispnik, после прочтения данного соообщения, указал ссылку на свой старый пост, в котором данная тема раскрыта горараздо лучше, при чём на основе теории и спецификаций (а не практики и эксперимента, как в моём случае). Прочитать можно здесь: http://lispnik.livejournal.com/9137.html
Комментариев нет:
Отправить комментарий