;;; Closures were a huge innovation. They depend on _lexical_ scoping, ;;; i.e., the idea that variables are resolved to their definitions at the ;;; moment of compilation/function creation, and the copies of these definitions ;;; are closed over. ;;; It was not always thus. In earlier LISPs, _dynamic_ scoping was the norm: variables ;;; were looked up by name at the point of execution, through the global symbol table. ;;; Local definitions in LETs temporarily overshadowed the global binding, but ;;; all name/symbol/variable resolution depended on the lookup by name, at each ;;; point of reference. This system was simple but occasionally confusing. ;;; Before we look at that, though, we'll want to learn one technical trick of ;;; switching between functions with multiple arguments and functions of just one ;;; argument. This trick is called Currying: a function of many arguments is just ;;; a series of closures of one-argument lambdas. (setq m (lambda (x) (lambda (y) (* x y)))) (closure (t) (x) #'(lambda (y) (* x y))) (funcall m 3) ; this is a one-argument function that multiplies its argument by 3 ; It's a partially applied + with one argument fixed (closure ((x . 3)) (y) (* x y)) ;; Mathematically, this is trivial: just holding one of the two arguments constant ;; obviously gets us a function of one argument. In math, such functions are free :) ;; In CS, we tend to think of functions as objects that take up memory, but functional ;; languages can make these costs cheap enough to keep thinking that functions ;; are actually almost free. This approach is amazingly helpful for structuring code! (funcall (funcall m 3) 5) ; this gets the expected result through application ; of two single-argument functions 15 ;; With this in mind we can look at this function that ultimately takes three arguments ;; to produce a number (setq ll (lambda (x) (let ((y 1)) (lambda (z) (let ((n 1)) (lambda (s) (* x y z s n))))))) (closure (t) (x) (let ((y 1)) #'(lambda (z) (let ... ...)))) ;; We'll apply it to some numbers: (funcall ll 5) (closure ((y . 1) (x . 5)) (z) (let ((n 1)) #'(lambda (s) (* x y z s n)))) (funcall (funcall ll 5) 6) (closure ((n . 1) (z . 6) (y . 1) (x . 5)) (s) (* x y z s n)) ;; Note that the lexically scoped variables are all captured by the closure. ;; eLisp shows them as dotted pair cons cells. (funcall (funcall (funcall ll 5) 6) 10) 300 ;;; Let's look at this more carefully (disassemble-internal ll 2 nil) doc: ... args: (arg1) ; outer LAMBDA, argument "x" 0 constant 1 ; [ 1 x ] 1 constant make-closure ; [ make-closure 1 x ] 2 constant ; [ compiled_middle_lambda make-closure 1 x ] doc: ... args: (arg1) ;; middle LAMBDA, argument "z" 0 constant 1 ;; [ 1 z ] 1 constant make-closure ; [ make-closure 1 z ] 2 constant doc: ... args: (arg1) ;; inner LAMBDA, argument "s" 0 constant * ; [ * s ] 1 constant V3 ; [ closure_var_3 * s ] 2 constant V2 ; [ closure_var_2 closure_var_3 * s ] 3 constant V1 ; [ closure_var_1 closure_var_2 closure_var_3 * s ] 4 stack-ref 4 ; [ s closure_var_1 closure_var_2 closure_var_3 * s ] ; this is the usual trick of keeping a clean copy of arguments ; at the bottom of the stack 5 constant V0 ; [ closure_var_0 s closure_var_1 closure_var_2 closure_var_3 * s ] 6 call 5 ; multiplies all of the above 7 return ; [ inner_lambda_bytecode make-closure 1 z ] 3 stack-ref 2 ; [ 1 inner_lambda_bytecode make-closure 1 z ] 4 stack-ref 4 ; [ z 1 inner_lambda_bytecode make-closure 1 z ] 5 constant V0 ; [ closure_var_0 z 1 inner_lambda_bytecode make-closure 1 z ] 6 constant V1 ; [ closure_var_1 closure_var_0 z 1 inner_lambda_bytecode make-closure 1 z ] 7 call 5 ; call to make_closure, makes middle_lambda with copies of n, z, y, and x 8 return ; [ middle_lambda_bytecode make-closure 1 x ] 3 stack-ref 2 ; [ 1 middle_lambda_bytecode make-closure 1 x ] 4 stack-ref 4 ; [ x 1 middle_lambda_bytecode make-closure 1 x ] 5 call 3 ; call make-closure with x and a copy of y 6 return ; return the closure nil ;; In the above example I unfortunately made "y" and "n" have the same value of 1, ;; so it's not evident from the disassembly which is which. ;; Follow through the following example to locate their appearances. (setq ll1 (lambda (x) (let ((y 100)) (lambda (z) (let ((n 200)) (lambda (s) (* x y z s n))))))) (closure (t) (x) (let ((y 100)) #'(lambda (z) (let ... ...)))) (funcall (funcall (funcall ll1 5) 6) 10) 6000000 (disassemble-internal ll1 2 nil) doc: ... args: (arg1) 0 constant 100 1 constant make-closure 2 constant doc: ... args: (arg1) 0 constant 200 1 constant make-closure 2 constant doc: ... args: (arg1) 0 constant * 1 constant V3 2 constant V2 3 constant V1 4 stack-ref 4 5 constant V0 6 call 5 7 return 3 stack-ref 2 4 stack-ref 4 5 constant V0 6 constant V1 7 call 5 8 return 3 stack-ref 2 4 stack-ref 4 5 call 3 6 return nil (byte-compile ll1) #[257 "\300\301\302#\207" [100 make-closure #[257 "\302\303\304\300\301%\207" [V0 V1 200 make-closure #[257 "\304\303\302\301\300%\207" [V0 V1 V2 V3 *] 7 " (fn S)"]] 8 " (fn Z)"]] 6 " (fn X)"] ;;;;;; All of this works thanks to the _lexical binding_, which binds x, y, x, and n as above. lexical-binding t ;;;;; However, Emacs started with dynamic binding, and can revert to it by setting one ;;;;; magic variable: (setq lexical-binding nil) nil ;;; Now the result of byte-compiling the above lambdas will be hugely different: (setq ll2 (lambda (x) (let ((y 100)) (lambda (z) (let ((n 200)) (lambda (s) (* x y z s n))))))) (lambda (x) (let ((y 100)) #'(lambda (z) (let ... ...)))) (closure (t) (x) (let ((y 100)) #'(lambda (z) (let \... \...)))) (disassemble-internal ll2 2 nil) args: (x) 0 constant args: (z) 0 constant args: (s) 0 constant * 1 varref x ; look up x in the global symbol table at this point 2 varref y ; .. and so for the other variable names 3 varref z 4 varref s 5 varref n 6 call 5 7 return 1 return 1 return nil (funcall ll2 5) (lambda (z) (let ((n 200)) #'(lambda (s) (* x y z s n)))) ;; Notice no closures. No values are saved, lookup by name is all we get. (funcall (funcall ll2 5) 6) (lambda (s) (* x y z s n)) ;; This means that prior values supplied to x and z and y and n don't help. ;; The above lambda needs to be able to look up all of these variables _at the ;; point of evaluation_. (funcall (funcall (funcall ll2 5) 6) 10) ; errors out because x, y, .. aren't defined globally (setq x 5) 5 (setq y 6) 6 (setq z 10) 10 (setq n 200) 200 ;; and not it works (funcall (funcall (funcall ll2 5) 6) 10) 600000 ;; Emacs exposes the global symbol table, as "obarray". obarray [0 0 flycheck-list-errors timer-next-integral-multiple-of-time vietnamese-tcvn-unix 0 0 make-mode-line-mouse-map backquote-listify sgml-mode find-sibling-rules help-follow-symbol ...] ;; Its printable representation is non-obvious to interpret, but we can run a special ;; function, MAPATOMS, over its elements (defvar syms nil) nil (mapatoms (lambda (s) (setq syms (cons s syms)))) nil syms (utf-16-le-dos pr-ps-file-preview flycheck-list-errors utf-7-mac timer-next-integral-multiple-of-time :log vietnamese-tcvn-unix make-mode-line-mouse-map backquote-listify sgml-mode non-ascii selected ...) (length syms) 24504 ;; When we add a new global symbol, we'll see it added to the obarray: (defvar totally-new-variable 1000) totally-new-variable (defvar syms nil) (mapatoms (lambda (s) (setq syms (cons s syms)))) (length syms) 24505 ;; now with one more! ;; Now back to lexical binding (setq lexical-binding t) t