[comp.lang.lisp] A small brush fire ...

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)