home *** CD-ROM | disk | FTP | other *** search
/ NetNews Usenet Archive 1992 #26 / NN_1992_26.iso / spool / comp / lang / lisp / 2824 < prev    next >
Encoding:
Text File  |  1992-11-08  |  10.2 KB  |  268 lines

  1. Path: sparky!uunet!charon.amdahl.com!pacbell.com!ames!agate!doc.ic.ac.uk!uknet!edcastle!aiai!jeff
  2. From: jeff@aiai.ed.ac.uk (Jeff Dalton)
  3. Newsgroups: comp.lang.lisp
  4. Subject: Re: the "upward funarg" problem
  5. Message-ID: <7867@skye.ed.ac.uk>
  6. Date: 6 Nov 92 18:05:32 GMT
  7. References: <Nov.2.08.30.54.1992.10743@brushfire.rutgers.edu> <1992Nov2.205747.1162@wstar.mixcom.com>
  8. Sender: news@aiai.ed.ac.uk
  9. Organization: AIAI, University of Edinburgh, Scotland
  10. Lines: 256
  11.  
  12. In article <1992Nov2.205747.1162@wstar.mixcom.com> jpf@wstar.mixcom.com (John P. Flanagan) writes:
  13. >gaynor@brushfire.rutgers.edu (Silver) writes:
  14. >
  15. >>I'd appreciate it if someone would describe the "upward funarg" problem,
  16. >>seemingly in reference to the resolution of free variable references.
  17. >>What is the problem, and what are its solutions?
  18. >
  19. >The upward funarg problem is the situation in which a function closure
  20. >loses the bindings for it's free variables.  The solution was to use
  21. >lexical scoping (as opposed to dynamic scoping).
  22.  
  23. That more or less right, but it's not the whole story.
  24.  
  25. It's possible to use lexical (aka static) scoping and still fail
  26. to solve the upward funarg problem (or even some aspects of the
  27. downward one).  C is an example.  Compiled Franz Lisp is another.
  28. VAX NIL used lexical scoping even in the interpreter (which was
  29. unusual at the time), but still (initially) had only "downward
  30. funargs".
  31.  
  32. It's also possible to solve the funarg problems while still using
  33. dynamic scoping.  Lisp 1.5 is an example.
  34.  
  35. We can go way back in Lisp history on this, and, ok, I will.  
  36. I think the following account is essentially correct, though
  37. some might dispute the details.  (And I'll omit InterLisp.)
  38.  
  39. Think of a Lisp with dynamic scoping.
  40.  
  41. Consider a function: (lambda (x) (cons x lis))
  42.  
  43. Consider a definition of MAPCAR:
  44.  
  45.    (defun mapcar (fun lis)
  46.      (if (null lis)
  47.          '()
  48.        (cons (funcall fun (car lis))
  49.              (mapcar fun (cdr lis)))))
  50.  
  51. Now:
  52.  
  53.    (let ((lis '(green dogs)))
  54.      (mapcar '(lambda (x) (cons x lis)) '(two three)))
  55.  
  56. We expect: ((two green dogs) (three green dogs)).
  57.  
  58. But we get: ((two two three) (three three)).
  59.  
  60. Why?  The functional argument refers to a "free variable" (namely LIS)
  61. that is bound by MAPCAR as well as by the LET.  We want this variable
  62. to refer the the LET binding, but because we're in a dynamically
  63. scoped Lisp it ends up referring to the MAPCAR binding instead.
  64.  
  65. So: we have a functional argument (hence "funarg") and a problem
  66. (hence "funarg problem"), and we're passing the functional argument
  67. "down" to a function we call rather than returning it "up" to a
  68. something that called us (hence "downward funarg problem").
  69.  
  70. In the upward case, we really talking about a functional value rather
  71. than a functional argument.  But the term "upward funarg problem"
  72. still makes sense (historically), as I will now explain.
  73.  
  74. The name FUNARG goes back to Lisp 1.5.  In Lisp 1.5, the value of
  75. (QUOTE (LAMBDA ...) was just (LAMBDA ...).  You'll note that I quoted
  76. the lambda-expression when calling MAPCAR above.  However (still in
  77. Lisp 1.5), the value of
  78.  
  79.   (function (lambda ...))
  80.  
  81. was
  82.  
  83.   (funarg (lambda ...) <env>)
  84.  
  85. If you're familiar with closures, you'll recognize this FUNARG list
  86. as a closure.  In Lisp 1.5, however, the environment was the a-list
  87. used for _dynamic_ scoping.
  88.  
  89. This solves the downward problem.  When I write
  90.  
  91.    (let ((lis '(green dogs)))
  92.      (mapcar (function (lambda (x) (cons x lis)))
  93.              '(two three)))
  94.  
  95. MAPCAR is called with two arguments:
  96.  
  97.  arg-1: (funarg (lambda (x) (cons x lis))  ; the lambda-expr
  98.                 ((lis . (green dogs))))    ; the a-list
  99.  
  100.  arg-2: (two three)
  101.  
  102. Moreover, the rule for applying FUNARGs was to use the a-list in
  103. the funarg as the environment.  So when the FUNARG is called by
  104. MAPCAR, the variable LIS gets its value from the a-list.  (If you
  105. write a simple Lisp interpreter, or copy the one in the Lisp 1.5
  106. book, use dynamic scoping, and use this rule for evaluating calls
  107. to FUNARGS, you'll see that it works.)
  108.  
  109. Now, since the a-list is an ordinary Lisp list, and since,
  110. consequently, it can be returned as a value, the whole FUNARG
  111. list can be returned as a value w/o any danger of the bindings
  112. captured in the a-list being lost.  So this FUNARG technique
  113. also solves the problem of functional results.  That is, it
  114. also works in the upward direction.  For instance,
  115.  
  116.   (defun make-counter (n)
  117.     (function (lambda () (setq n (1+ n)))))
  118.  
  119.   (make-counter 10)
  120.     ==> (funarg (lambda () (setq n (1+ n))) ((n . 10)))
  121.  
  122. This funarg carries its interpretation of N with it wherever it
  123. goes.
  124.  
  125. So this is great!  All the problems are solved!
  126.  
  127. Unfortunately, Lisp implementors wanted Lisp to be more efficient.
  128. They wrote compilers.  They put variable values on a stack and
  129. compiled references to the variables using stack offsets computed
  130. as compile-time, just like compilers for languages like C.  In
  131. effect, this implements lexical scoping for local, bound, variables.
  132.  
  133. Free variables were still a problem, though.  A "free variable"
  134. is something that we'd call "a reference to a global variable"
  135. in a language that didn't allow one function definition to appear
  136. inside another.  That sort of language was one that people knew
  137. how to handle.  (The ones that allowed nested function definitions
  138. were trickier.)  So Lisp compilers more or less behaved as if they
  139. were dealing with that kind of language.
  140.  
  141. A lambda-expr inside a function definition is a lot like a nested
  142. function definition.  That was hard to handle.  So the compilers
  143. compiled the lambda-expr as if it had appeared on its own at the
  144. top level and not inside another function at all!  And if the
  145. lambda-expr function wanted to refer to any of the local variables
  146. of the function in which the lambda-expr appeared (vars like N in
  147. MAKE-COUNTER above, or LIS in the LET even further back), it couldn't.
  148. In compiled code, those variables were just stack entries.  The
  149. old a-list and FUNARG trick wouldn't work.
  150.  
  151. Some clever noticed that, despite all this, they could still solve the
  152. downward case.  Instead of having (FUNCTION (LAMBDA ...)) return
  153.  
  154.    (funarg (lambda ...) <a-list>)
  155.  
  156. it could return
  157.  
  158.    (*funarg (lambda ...) <stack-pointer>)
  159.  
  160. So Lisps like this had downward funargs that worked (with the aid
  161. of some clever tricks), but they didn't have upward funargs.  That is,
  162. if they returned one of these *FUNARG lists, the stack would still be
  163. popped (as on all function returns), the local vars would be gone,
  164. and the stack-pointer would be useless.
  165.  
  166. PDP-10 MacLisp was a Lisp of roughly this sort, as was Franz Lisp
  167. (though Franz lacked the downward funarg trick).  The Lisp 1.5
  168. compiler put Lisp 1.5 in roughly the same situation too, but Lisp 1.5
  169. still had upward funargs for certain cases.
  170.  
  171. These Lisps typically used SPECIAL declarations to tell the compiler
  172. to have the emitted code treat certain variables in the way the
  173. interpreter would treat them rather than have the variables turned
  174. into stack offsets.  SPECIAL variables were, consequently, dynamically
  175. scoped (because that's what interpreters did in those days).
  176.  
  177. So here's one solution to the upward funarg problem for special
  178. variables:
  179.  
  180.   * Use an a-list for special variables and put the current
  181.     a-list in any FUNARGS that are created.
  182.  
  183. However, Lisps that wanted to me more efficient (eg, MacLisp) 
  184. stopped using a-lists for special variables.  A-lists are a 
  185. form of "deep binding".  You have to look back along the a-list 
  186. for the variable you want.  "Shallow binding" works by attaching
  187. the values directly to the symbols that represent the variables
  188. and storing the old value on a stack whenever a nested binding
  189. is made.  For instance, 
  190.  
  191.   (let ((a 1))
  192.     (declare (special a))
  193.     (g))
  194.  
  195. is more or less equivalent to:
  196.  
  197.   (push (symbol-value 'a) <stack>)
  198.   (unwind-protect
  199.       (progn (setf (symbol-value 'a) 1)
  200.              (g))
  201.     (setf (symbol-value 'a) (pop <stack>)))
  202.  
  203. This makes for fast, constant-time lookup, though it's somewhat slow
  204. at setting up bindings and taking them down again.  But it prevents
  205. the above solution to the upward funarg problem.
  206.  
  207. In any case, a feature of the a-list approach is that the a-list
  208. contains _all_ the non-top-level bindings.  So there's no need to
  209. figure out what variables need to be included in the FUNARG.  In some
  210. shallow-binding Lisps (which means no a-list), it was possible to make
  211. a form of dynamic-binding closure by explicitly listing the variables
  212. to "close over".  Various tricks could make this more, or less, close
  213. to the semantics of the a-list solution.  I won't go into the details,
  214. but examples are Franz Lisp and ZetaLisp (Lisp Machine Lisp).  A
  215. version that works more or less correctly _except when there are
  216. assignments to the variables that are closed over_ is as follows:
  217.  
  218. (defun make-closure (vars fun)
  219.   (let ((vals (mapcar #'symbol-value vars)))
  220.     `(lambda (&rest args)
  221.        (let ,(mapcar #'(lambda (var val) `(,var ',val)) vars vals)
  222.          (declare (special ,@vars))
  223.          (apply ',fun args)))))
  224.  
  225.  
  226. > (let ((a 1) (b 2))
  227.     (declare (special a b))
  228.     (make-closure '(a b) '(lambda (c) (list a b c))))
  229.  
  230. (lambda (&rest args)
  231.   (let ((a '1) (b '2))
  232.     (declare (special a b))
  233.     (apply '(lambda (c) (list a b c)) args)))
  234.  
  235. > (funcall * 3)
  236.  
  237. (1 2 3)
  238.  
  239. This is pretty much how things stood in the early 80s, until Common
  240. Lisp came along and took the idea of full lexical scoping from Scheme.
  241. Compilers had to be more sophisticated if they wanted to make this
  242. work efficiently.  They had to identify which variables were going
  243. to be closed over and arrange for them to have their values in some
  244. heap-allocated structure rather than on the stack.  The emitted code
  245. had to know how to refer to entries in this structure.  And so on.
  246.  
  247. However, in the the interpreter, a lexical solution can be very
  248. like the Lisp 1.5 a-list solution described above.
  249.  
  250.              +--------------------------------+
  251.              | Lexical scoping as if by magic |
  252.              +--------------------------------+
  253.  
  254. It works like this:
  255.  
  256.   1. Write an ordinary, dynamically scoped interpreter with an a-list.
  257.   2. Implement the FUNCTION / FUNARG technique from Lisp 1.5.
  258.   3. Have (DEFUN <name> <args> <form>+) be equivalent to
  259.  
  260.        (setf (symbol-function '<name>)
  261.              (function (lambda <args> <form>+)))
  262.  
  263. That is, functions include their definition environment.
  264.  
  265. Jeff Dalton,
  266. AI Applications Institute,                               J.Dalton@ed.ac.uk
  267. Edinburgh University.
  268.