From: Bruno H. <br...@cl...> - 2017-12-23 21:31:27
|
Hi Sam, > I wonder if something like "lazy-binding" could help: bind the variable > but not initialize its value. until it is explicitly set, accessing it > reaches up in the environment. This would require something like > `lazy-let` with the following test case: > > --8<---------------cut here---------------start------------->8--- > (let ((a 1)) > (lazy-let (a b) > (print a) ; prints 1 > (print b) ; error - unbound variable > (setq a 10) ; inner binding of a is activated > (print a) ; prints 10 > (setq b 22)) ; binding of b is activated > (print b) ; error - unbound variable > (print a)) ; prints 1 > --8<---------------cut here---------------end--------------->8--- > > while it is easy to avoid `lazy-let` above by putting `let` after the > second `print`, it is not so easy with `loop` because we need the > binding to exist around the main `tagbody` but to do initialization in > the preamble inside `tagbody`. Your proposal introduces a new approach to scoping: Have the scope depend on whether the access to a variable is a read or a write access. While this may provide a small advantage in implementing LOOP, I believe that it is a really bad idea in general. 1) The purpose of the scopes in the language definition is that by looking a the (fully macroexpanded) source code of a function, the user can tell to which binding a certain variable reference refers. If the user furthermore has to _scan_ the source code for assignments, this lookup is significantly more complicated. 2) What about conditional accesses? (lazy-let (a) (if (> (random) 0.5) (setq a 10)) (lambda (a) (print a))) 3) There are forms which combine a read access and a write access, like (INCF b). Instead, maybe we should provide explicit control of the environment in which a form will be evaluated/compiled. For example: (let ((a 1)) (environment-let env-with-a (let ((b 2)) (in-enviroment env-with-a (list a b))))) will evaluate (list a b) in an environment that contains the binding of a but not the binding of b. This would be equivalent to (let ((a 1)) (let ((#:g1 (lambda () (list a b)))) (let ((b 2)) (funcall #:g1)))) Such a clisp specific set of special-operators ENVIRONMENT-LET and IN-ENVIRONMENT could be emulated for other implementations through a code walker. Still, I don't know whether such a thing is really needed for the LOOP macro, where we have a finite set of initforms and bindings. > The problem with loop is that under certain circumstances we separate > initializing and binding of a variable. This is not a problem by itself. It is explicitly allowed by CLHS 6.1.1.4 "Implementations can interleave the setting of initial values with the bindings." What I see as a problem is that users are not warned when they write unportable code. That is, we should have a warning (in the spirit of "gcc -fsanitize=undefined" for C) whenever the interpretation of an initform actually depends on the environment. Bruno |