But just as a safety precaution against ad-hoc diddling in the REPL, I invented a safety valve macro:
(defun ?specials (syms)
;; give us a list of symbols
;; return a list of those which are
;; declared special as viewed from current package
(loop for sym in syms
when (sys:declared-special-p sym)
collect sym))
(defmacro error-if-special (&rest syms)
;; place (user:error-if-special ...syms...) in your code
;; Compiler will issue an error if any of them are SPECIAL bindings
;; Most useful with multithreaded code to be sure we don't accidentally
;; refer to a special binding instead of a lexical binding.
(let ((specs (?specials syms)))
(when specs
(error "Symbols are SPECIAL: ~A" specs))))
The ?SPECIALS is useful from the keyboard if you want to check anticipated use of symbols. But the ERROR-IF-SPECIAL macro is good to plant permanently in the source of multithreaded exploits. A typical use scenario is the following example:
…
(let* ((sem (mp:make-semaphore :count 0))
...
(multiple-value-bind (short-list rest-list)
(nsplit-list nshort fns)
(user:error-if-special fn sem)
(map nil (lambda (fn)
(mp:funcall-async (lambda ()
(unwind-protect
(funcall fn)
(mp:semaphore-release sem)))
))
rest-list)
…
Here, the lambda being sent to funcall-async internally refers to values belonging to bindings named fn and sem. Both of these were intended to be seen as lexical bindings in this body of code. But if some inadvertent keyboard diddling causes one or both to be specially bound (you can’t ever know what will happen in the future), and if this body of code is subsequently recompiled from its source, the macro error-if-special will abort the recompilation with an error.
My guess, about the asymmetry in the DECLARE clauses - i.e., declare special, without a corresponding declare lexical, arose from history, and too much old code would break unless we just automatically compiled references to extant special bindings in precedence over lexical bindings. I, personally, don’t much like dealing with accidents waiting to happen, but what can you do?
- DM
Thanks for those additional hints and tools.
I guess the most alarming thing to me is that I have relied too long on thinking that I was performing intensional programming, where I thought that syntax dictates semantics. That simply isn’t true unless you abide strictly by the earmuff convention. To do otherwise invites the possibility of nondeterminism due to inadvertent name collisions at the REPL listener.
In a single thread, my ignorance was masked by making sometimes dynamic bindings locally, when I didn’t realize I was doing so. The code worked the way I expected within the lexical frame. However, any other functions that might have relied on the value of that global binding might have received a surprise, depending on when they looked.
The multithreaded environment finally opened my eyes, where each thread has its own dynamic binding environment. I gather that is because these dynamic bindings are held in stack frames, and threads each have their own stack.
The situation almost makes me want to define DLET and LLET macros so that I can return to intensional programming.
- DM
(hcl:variable-information '*read-eval*)
:SPECIAL
NIL
NIL
Br
/Alexey