Неожиданное поведение с `eval-when`

Следующий фрагмент кода CL не работает, как я ожидал, с CCL с SLIME. Если я сначала скомпилировать и загрузить файл с помощью C-c C-k, а затем запустить

(rdirichlet #(1.0 2.0 3.0) 1.0)

в SLIME/CCL REPL я получаю ошибку

value 1.0 is not of the expected type DOUBLE-FLOAT.
   [Condition of type TYPE-ERROR]

Работает с SBCL. Я ожидал (setf *read-default-float-format* 'double-float)) чтобы позволить мне использовать такие значения, как 1.0, Если я загружу этот файл в CCL, используя LOAD на REPL это работает. Что мне не хватает?

(eval-when (:compile-toplevel :load-toplevel :execute)
  (require :asdf) (require :cl-rmath) (setf *read-default-float-format* 'double-float))

(defun rdirichlet (alpha rownum)
  ;;(declare (fixnum rownum))
  (let* ((alphalen (length alpha))
    (dirichlet (make-array alphalen :element-type '(double-float 0.0 *) :adjustable nil :fill-pointer nil :displaced-to nil)))
    (dotimes (i alphalen)
      (setf (elt dirichlet i) (cl-rmath::rgamma (elt alpha i) rownum)))
    ;; Divide dirichlet vector by its sum
    (map 'vector #'(lambda (x) (/ x (reduce #'+ dirichlet))) dirichlet)))

ОБНОВЛЕНИЕ: я забыл упомянуть мою платформу и версии. Я использую Debian Squeeze x86. Версия SLIME от Debian нестабильна, 1:20120525-2, CCL - это релиз 1.8. Я попробовал это как с исходными двоичными файлами от http://svn.clozure.com/publicsvn/openmcl/release/1.8/linuxx86/ccl, так и с созданным мной двоичным пакетом - см. Package ccl на mentors.debian.net. Результат был одинаковым в каждом случае.

Кажется вероятным, что эта проблема специфична для SLIME. Было бы полезно, если бы люди могли прокомментировать, видят ли они это поведение или нет. Кроме того, что является эквивалентом C-c C-k в SLIME, если один работает CCL в режиме основной командной строки? (LOAD filename), или что-то другое? Или, чтобы задать немного другой вопрос, что такое функция CCL C-c C-k звонишь?

Я замечаю, что звонит C-c C-k по следующему коду

(eval-when (:compile-toplevel :load-toplevel :execute)
      (require :asdf) (require :cl-rmath) (setf *read-default-float-format* 'double-float))

(print *read-default-float-format*)

производит DOUBLE-FLOAT хотя хотя *read-default-float-format* на REPL сразу после дает SINGLE-FLOAT,

ОБНОВЛЕНИЕ 2: похоже, как сказал Райнер, компиляция происходит в отдельном потоке.

По функции all-processes в словаре тем

печать all-processes из буфера с помощью Cc Ck дает

(#<PROCESS worker(188) [Active] #x18BF99CE> #<PROCESS repl-thread(12) [Semaphore timed wait] #x187A186E> #<PROCESS auto-flush-thread(11) [Sleep] #x187A1C9E> #<PROCESS swank-indentation-cache-thread(6) [Semaphore timed wait] #x186C128E> #<PROCESS reader-thread(5) [Active] #x186C164E> #<PROCESS control-thread(4) [Semaphore timed wait] #x186BE3BE> #<PROCESS Swank Sentinel(2) [Semaphore timed wait] #x186BD0D6> #<TTY-LISTENER listener(1) [Active] #x183577B6> #<PROCESS Initial(0) [Sleep] #x1805FCCE>)
CL-USER> (all-processes)

и в REPL дает

(#<PROCESS repl-thread(12) [Active] #x187A186E> #<PROCESS auto-flush-thread(11) [Sleep] #x187A1C9E> #<PROCESS swank-indentation-cache-thread(6) [Semaphore timed wait] #x186C128E> #<PROCESS reader-thread(5) [Active] #x186C164E> #<PROCESS control-thread(4) [Semaphore timed wait] #x186BE3BE> #<PROCESS Swank Sentinel(2) [Semaphore timed wait] #x186BD0D6> #<TTY-LISTENER listener(1) [Active] #x183577B6> #<PROCESS Initial(0) [Sleep] #x1805FCCE>)

Ну, это похоже #<PROCESS worker(188) [Active] #x18BF99CE> это поток, который делает компиляцию. Конечно, все еще остается вопрос о том, почему эти переменные являются локальными для потока, а также почему SBCL не ведет себя так же.

1 ответ

Решение

Я вижу это с CCL и некоторыми более старыми (которые я использую) SLIME тоже. Не пробовал с новым SLIME.

Это не происходит с SBCL или LispWorks.

*read-default-float-format* является одной из переменных ввода-вывода Common Lisp. Что-то вроде WITH-STANDARD-IO-SYNTAX связывает их со стандартными значениями и при выходе восстанавливает предыдущие значения. Поэтому я подозреваю, что SLIME-код CCL имеет такую ​​привязку. Это подтверждается настройкой других переменных ввода / вывода, таких как *read-base* - они также связаны.

CCL, Emacs и SLIME имеют несколько уровней кода, что делает его несколько сложным для отладки.

  • Emacs запускает код ELISP SLIME и обращается к SWANK.
  • SWANK является бэкэндом SLIME и является кодом Common Lisp, работающим в CCL.
  • SWANK имеет переносимый код и некоторый специфичный для CCL код.

На стороне Emacs функция SLIME/ELISP SLIME-COMPILE-AND-LOAD-FILE используется.

На стороне SWANK функция Common Lisp swank:compile-file-for-emacs вызывается. Потом SWANK:LOAD-FILE вызывается.

Я не вижу, где связаны переменные ввода / вывода - может быть, это в сетевом коде CCL?

Это, кажется, ответ:

Если CCL имеет локальные переменные ввода / вывода потока, то компиляция происходит в другом потоке и не изменит привязки ввода / вывода в потоке REPL, а также в любом другом потоке.

Это разница между различными реализациями CL. Поскольку стандарт CL не определяет потоки или процессы, он также не указан, если специальные привязки являются глобальными или имеют локальную привязку потоков по умолчанию...

Если подумать, то защита переменных ввода-вывода потока от изменений других потоков имеет смысл...

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


Позвольте мне немного рассказать, почему вещи такие, какие они есть.

Обычно реализация Common Lisp может запускать более одного потока. В некоторых реализациях также допускается одновременное выполнение потоков на разных ядрах. Эти потоки могут выполнять очень разные вещи: один может запустить REPL, другой может ответить на HTTP-запрос, один может загрузить данные с диска, а другой - прочитать содержимое электронного письма. В этом случае Lisp выполняет несколько разных задач в одной системе Lisp.

В Лиспе есть несколько переменных, которые определяют поведение операций ввода-вывода. Например, какой формат является плавающим при чтении, или какие базовые целые числа находятся при чтении. Письмо сделано `read-base.

Теперь представьте, что вышеупомянутый поток чтения с диска установил его *read-base* до 16 для какой-то цели. Теперь вы изменяете глобальное значение в другом потоке на 8, а затем внезапно все остальные потоки имеют базу 8. Следствие: поток чтения с диска внезапно увидит *read-base* 8 вместо 16 и работают по другому.

Так что имеет смысл предотвратить это каким-то образом. Самым простым является то, что в каждом потоке исполняемый код имеет свои собственные привязки для значений ввода / вывода, а затем изменяет *read-base* не будет влиять на другие темы. Эти привязки обычно вводятся LET или вызов функции. Обычно код отвечает за привязку переменных.

Другой способ предотвратить это - дать каждому потоку несколько начальных привязок, которые, например, должны включать привязки ввода / вывода. CCL делает это. Например, LispWorks тоже это делает. Но не для переменных ввода / вывода.

Теперь каждый Лисп может дать вам непереносимый способ изменить локальную верхнюю привязку потока (у CCL это тоже есть - например, (setf ccl:symbol-value-in-process)). Тем не менее, это не означает, что это может изменить обязательную силу в REPL. Поскольку сам REPL является частью кода на Лиспе, он выполняется в потоке и может устанавливать свои собственные привязки.

В CCL вы также можете установить глобальную статическую привязку: (CCL::%SET-SYM-GLOBAL-VALUE sym value), Но если вы используете такую ​​функциональность, вы, вероятно, делаете что-то не так или у вас есть веская причина.

Некоторый фон для CCL: http://clozure.com/pipermail/openmcl-devel/2011-June/012882.html

уздечка

  • Не пытайтесь изменить глобальные привязки из одного потока, видимого для других потоков.
  • Защитите свой код от изменений в других потоках, связав важные переменные с правильными значениями.
  • Напишите функцию, которая устанавливает значения переменных для некоторых значений контролируемым образом.
Другие вопросы по тегам