For Penknife I instead intend to explore the use of first-class interaction environments--where the interaction environment contains both a global environment and a reader--for exactly the purpose of allowing reader extensions to be localized to specific library loads, among other purposes.
However, I don't expect this approach to get much farther than you're talking about for the purpose of local hacks, in the sense of a library wanting to change the behavior of A by modifying B while the programmer loading the library wants to continue using the original behaviors. In this case, I think either the programmer needs to conjure up doppelgangers for both A and B (generally a kinda arduous task I bet, even with reflection and first-class environments to make it possible), or else the original A and B bindings need to be given to the library and reset to their old values once the library's done--which is exactly what you're doing.
Okay, you're using continuation-and-thread-aware dynamic scope, and I'm leaving it open to the possibility of ad hoc dynamic scope using everyday global variables. As long as Penknife has no threads or reentrant continuations, it's the same thing, right? :)
Speaking of it being the same thing, have you considered just making every global implicit? It might slow things down considerably (I don't know), and it would only matter if you rigged up multiple racing threads to load files or jumped between files using continuations, but it might at least feel more elegant. :-p
have you considered just making every global implicit?
An intriguing idea!
Imagine for example you wanted to find out how often one part of your code was directly or indirectly calling "map", but you didn't all the other concurrent calls to "map" to be recorded. Easy, just parameterize "map" to do the tracing you want within the call to your code; and everything else is unaffected.
I haven't heard of an implementation of CL with call/cc (but maybe I just haven't looked very hard XD ). Because of that, I think simulating a Racket parameter in Common Lisp would be just the same as simulating a Racket preserved thread cell (i.e. a thread-local box which is initialized in each thread to the value it had in the parent thread).
If 'defvar (and 'defparameter too?) dynamic variables have per-thread state, the next question is whether they're "preserved," and after that it's a matter of whether they have their own quirky properties or additional features apparent only in context. For instance, a Racket parameterization may or may not map well to a Common Lisp dynamic environment.
But maybe the more important question is: What do you expect from an implicit variable, and is 'defvar sufficient for that? For my purposes, any Arc global variable is (usually) sufficient, and I use Lathe's 'w/global to rebind one for a naive dynamic extent.