jds@duke.UUCP (Joseph D. Sloan) (11/20/86)
So how would you answer the following: Since "good" programmers know better than using free (== global) variables, the argument between lexical and dynamic scoping is really a mute point. The real issue this: Can you exhibit a piece of code that depends on the scoping rules that can't be coded AS WELL in a way that doesn't depend on the scoping rules? duke!jds
steele@unc.UUCP (Oliver Steele) (11/21/86)
In article <8868@duke.duke.UUCP> jds@duke.UUCP (Joseph D. Sloan) writes: > > So how would you answer the following: Since "good" >programmers know better than using free (== global) variables, >the argument between lexical and dynamic scoping is really a >mute point. The real issue this: Can you exhibit a piece of code >that depends on the scoping rules that can't be coded AS WELL >in a way that doesn't depend on the scoping rules? > > duke!jds (defun mymapcar (myfunction list) (cond ((null list) nil) (t (cons (myfunction (car list)) ;or funcall, etc. (mymapcar myfunction (cdr list)))))) (defun consall (l list) (mymapcar '(lambda (x) (cons x list)) l)) lexical: (consall '(a b c) '(d)) ==> ((a d) (b d) (c d)) dynamic: (consall '(a b c) '(d)) ==> ((a a b c) (b b c) (c c)) The problem isn't so much that you don't get what you expected with dynamic scoping, but that what you get depends on the lambda variables used in the function you're calling. This is the same problem as using global variables: you have to know what variables the procedures you're calling use, instead of just what they do. e.g.: (defun consall2 (l llist) (mymapcar '(lambda (x) (cons x llist)) l)) ought to do the same thing as consall, but will give different results under dynamic scoping. -- Oliver Steele----------------------------------steele@unc When a tree dies, ...!{decvax,ihnp4}!mcnc!unc!steele plant another in its place. steele%unc@csnet-relay.csnet
rpk@lmi-angel.UUCP (Bob Krajewski) (11/24/86)
In article <> jds@duke.UUCP (Joseph D. Sloan) writes: > So how would you answer the following: Since "good" >programmers know better than using free (== global) variables, >the argument between lexical and dynamic scoping is really a >mute point. The real issue this: Can you exhibit a piece of code >that depends on the scoping rules that can't be coded AS WELL >in a way that doesn't depend on the scoping rules? > With lexical scoping, it's a lot easier to use local functions that refer to variables in the enclosing binding construct, where *those* variables are also intended to be local to the enclosing function. In other words, in lexically scoped Common Lisp we can write: (defun map-increment (list n) (mapcar #'(lambda (elt) (+ elt n)) list)) where the anonymous function makes a reference to N that is lexical, but N is bound by MAP-INCREMENT. In a MACLISP-style Lisp, where lexical scoping is foiled by lambda-expressions (i.e., local variables were only the ones that could be referred to by going up binding contours until you hit the establishment of a function), there are a few ways around this: 1. Handling situations that would want to use ``real'' lexical scoping specially. For the MAP family of functions, this could be handled by open-coding the loop, and transforming ((lambda (<vars>) <body>) <args>) into (let ((<var0> <arg0>) (<var1> <arg1>) ...) <body>) This strategy supported what people were likely to do with MAPCAR and friends. But if you had your own function-using function, and you were not a system implementor, you were out of luck and had to use one of the following strategies (or, if you are less charitable, kludges). 2. Declaring the needed variables globally special. (defun frobnicate-list (function list) (cons 'frobnicated-list (mapcar function list))) (defvar *frob*) ; No top-level value (defun frob-list (list *frob*) (frobnicate-list #'(lambda (elt) (frob-element elt *frob*)) list)) 3. Declaring the needed variables locally specially as needed. (local-declare ((special frob)) ; special form for local declarations (defun frobnicate-list (list frob) (frobnicate-list #'(lambda (elt) (frob-element elt frob)) list)) ) In 2 and 3, the dynamic scope of the extra variable will allow the lambda-expression to see the right value. In either of these cases, the programmer has to go out of his way to get around the inadequacies of such lazy scoping rules (which were different in the compiler and the interpreter). One nice technique that lexical scoping allows you is a functional way of implementing macros that wrap code. Basically, a handler function funcalls a local function (maybe with some other arguments), instead of open-coding what the macro is supposed to do. This technique can't work unless locally generated functions can access any lexically available variables with special declarations, fuss, or bother. Here is a simple example of the technique. The open-coding style would look like: (defmacro without-mangoes (&body body) `(unwind-protect (progn (shut-off-mangoes) ,@body) (turn-on-mangoes))) The functional style would look like: (defmacro without-mangoes (&body body) `(without-mangoes-1 #'(lambda () ,@body))) ;;; Here is the run-time support function (defun without-mangoes-1 (f) (unwind-protect (progn (shut-off-mangoes) (funcall f)) (turn-off-mangoes))) This is a good strategy when implementing a macro that has an elaborate wrapper which, if implemented in the straightforward way, would increase the size of the function that uses it by making references to more constants, generating local variables, etc. Also, only the support function has to be recompiled to effect a change, instead of recompiling all uses of the macro. Since the strategy will involve at least two additional function calls at run time, and cause references to local variables not immediately inside the current frame, it is less efficient, but probably not noticeably less so if used away from loop code. -- Robert P. Krajewski Internet/MIT: RPK@MC.LCS.MIT.EDU UUCP: ...{cca,harvard,mit-eddie}!lmi-angel!rpk
desj@brahms (David desJardins) (11/24/86)
In article <8868@duke.duke.UUCP> jds@duke.UUCP (Joseph D. Sloan) asks why
good code can require lexical rather than dynamic scoping.
(define (addn n)
(lambda (x) (+ x n)))
(define 2+ (addn 2))
(define 3+ (addn 3))
(2+ 4)
gives 6 (lexical) or 7 (dynamic).
In general, you need lexical scoping whenever you want bindings of actual
to formal parameters to exist beyond the scope of the current evaluation.
-- David desJardins
P.S. Read Abelson and Sussman.
willc@tekchips.UUCP (Will Clinger) (11/26/86)
In article <412@ethz.UUCP> ceb@ethz.UUCP (Charles Buckley) writes: >The code presented certainly depends on the scoping rules, but the >second part of the challenge remains unanswered, i. e. "can't be coded >AS WELL in a way that doesn't depend on the scoping rules." > >In fact, in the context of the (perforce) interpreted >lambda-expression which makes up the function pased as an argument, >list is a special variable which must be resolved by name at runtime, >an expensive process. Therefore, many would not consider the above an >example of good code at all. Mr Buckley misses the point. The lambda expression is interpreted only because it was quoted. I assume Mr Steele used the quote only to force dynamic scope rules to be used for testing, and that he forgot to remove the quote after testing. Mr Steele's point was that dynamic scope rules would make it impossible to write MYMAPCAR in such a way that it is independent of the names of variables bound in code that calls it. Mr Buckley continues: >One rule independent way of coding the above (I use Zetalisp syntax) is > >(defun mynewmapcar (function list-to-be-indexed &rest fixed-args) > (cond ((null list-to-be-indexed) nil) > (t (cons (lexpr-funcall function (car list-to-be-indexed) fixed-args) > (lexpr-funcall 'mynewmapcar function (cdr list-to-be-indexed) > fixed-args))))) This code is *not* independent of the scope rules. It will not work correctly if dynamic scope rules are used and CONSALL is defined by (defun consall (l list-to-be-indexed) (mynewmapcar 'cons l list-to-be-indexed)) If dynamic scope rules are used, it is simply impossible to write such procedures that will not break when called from code written by other programmers who have been unlucky in their choice of variable names. (Well, not quite impossible. I once got so fed up with this problem in MacLisp that I wrote a macro that took a DEFUN and substituted the bound variable names by gensyms throughout the DEFUN. But it's pretty crummy to have to resort to such lousy hacks.) For the benefit of people who don't speak Zetalisp, Mr Buckley's example would appear in Scheme as: (define (mynewmapcar function list-to-be-indexed . fixed-args) (cond ((null? list-to-be-indexed) '()) (else (cons (apply function (car list-to-be-indexed) fixed-args) (apply mynewmapcar function (cdr list-to-be-indexed) fixed-args))))) Syntax and typos aside, the only difference between Mr Buckley's example and Mr Steele's example is that Mr Buckley extended MYNEWMAPCAR to take more than two arguments. William Clinger Tektronix Computer Research Laboratory
bd@zyx.UUCP (Bjorn Danielsson) (11/27/86)
In article <8868@duke.duke.UUCP> jds@duke.UUCP (Joseph D. Sloan) writes: > > So how would you answer the following: Since "good" >programmers know better than using free (== global) variables, >the argument between lexical and dynamic scoping is really a >mute point. The real issue this: Can you exhibit a piece of code >that depends on the scoping rules that can't be coded AS WELL >in a way that doesn't depend on the scoping rules? > > duke!jds Dynamic scoping is quite useful in code like: (let ((*print-base* 2)) (print (stuff))) Or in general, when the dynamic binding of a global variable is used to pass a parameter to another function (in this case "print", or some function called by print), but the variable is never accessed locally. In most other cases where free variables occur, you should use closures: (defun consall (l list) (mymapcar (function (lambda (x) (cons x list))) l)) Because the closure is created when the dynamically visible variable "list" is the same as the lexically visible one, the difference between the two kinds of scoping disappear. -- Bjorn Danielsson, ZYX, +46 8 653205, ...mcvax!enea!zyx!bd
ceb@ethz.UUCP (Charles Buckley) (11/30/86)
In his posting of 21 November (our number 535 - the numbering schemes seem to play a time zone style game as the messages cross the Atlantic), Mr. Steele responds to Mr. Sloan's challenge (recalled here) > So how would you answer the following: Since "good" >programmers know better than using free (== global) variables, >the argument between lexical and dynamic scoping is really a >mute point. The real issue this: Can you exhibit a piece of code >that depends on the scoping rules that can't be coded AS WELL >in a way that doesn't depend on the scoping rules? > with the following redefinition of mapcar, and an application function using it: (defun mymapcar (myfunction list) (cond ((null list) nil) (t (cons (myfunction (car list)) ;or funcall, etc. (mymapcar myfunction (cdr list)))))) (defun consall (l list) (mymapcar '(lambda (x) (cons x list)) l)) lexical: (consall '(a b c) '(d)) ==> ((a d) (b d) (c d)) dynamic: (consall '(a b c) '(d)) ==> ((a a b c) (b b c) (c c)) The code presented certainly depends on the scoping rules, but the second part of the challenge remains unanswered, i. e. "can't be coded AS WELL in a way that doesn't depend on the scoping rules." In fact, in the context of the (perforce) interpreted lambda-expression which makes up the function pased as an argument, list is a special variable which must be resolved by name at runtime, an expensive process. Therefore, many would not consider the above an example of good code at all. One rule independent way of coding the above (I use Zetalisp syntax) is (defun mynewmapcar (function list-to-be-indexed &rest fixed-args) (cond ((null list-to-be-indexed) nil) (t (cons (lexpr-funcall function (car list-to-be-indexed) fixed-args) (lexpr-funcall 'mynewmapcar function (cdr list-to-be-indexed) fixed-args))))) (defun consall (l list) (mynewmapcar 'cons l list)) [lexpr-funcall is the same as funcall, except the last argument is used to terminate the argument list passed to the funcalled function, instead of being the last element in a nil terminated list.] The above behaves in the manner implied by Mr. Steele's example regardless of scoping rules, and supports Mr. Sloan's point about free (special) variables. Note I called the workhorse function 'new' mapcar, because the issue lurking behind al of this is that if the meta-functions which we have grown accustomed to were semantically more powerful, the brush fire would have remained as someones smoldering cigarette ashes. Dr. Charles E. Buckley uucp: mcvax!ethz!jungfrau!ceb earn: BUCKLEY@CZHETH5A Institut fuer Integrierte Systeme ETH-Zentrum CH-8092 Zuerich (Suisse) Tel: 01/256 5245 (national) +411 256 5245 (international) -- Dr. Charles E. Buckley uucp: mcvax!ethz!jungfrau!ceb earn: BUCKLEY@CZHETH5A Institut fuer Integrierte Systeme ETH-Zentrum CH-8092 Zuerich (Suisse) Tel: 01/256 5245 (national) +411 256 5245 (international)