> >> ... the "thunks" were necessary at the machine-language level to > >> /implement/ ALGOL 60, but they could not be expressed /in/ ALGOL. > > Ah, thanks for the clarification. Is that info in the appropriate > > WikiPedia page? If not, maybe you would edit it in? > From: John W Kennedy <[EMAIL PROTECTED]> > It is explained s.v. "thunk", which is referenced from "ALGOL > 60". The ALGOL "pass-by-name" argument/parameter matching was > perhaps the most extreme example ever of a language feature that > was "elegant" but insane. What it meant, in effect, was that, > unless otherwise marked, every argument was passed as two closures, > one that returned a fresh evaluation of the expression given as the > argument, which was called every time the parameter was read, and > one that set the argument to a new value, which was called every > time the parameter was set.
Wow! All these years when I occasionally heard of a "thunk" I never was told, until now, what it really meant. Thanks for the info!! Followup question #1: I assume these are lexical closures in the environment of the point of the call, right? Followup question #2: For simple arithmetic expressions, I can possibly understand how the UPDATE closure might be implemeted (expressed in Lisp to make the intent clear): Call form: MyFunction(X+2); GET closure: (+ closedX 2) UPDATE closure: (lambda (newval) (setf closedX (- newval 2)) Thus from inside MyFunction where formal parameter Arg1 is bound to actual parameter X+2, after doing Arg1 := 7; X will have the value 5 so that calling Arg1 will return 7 as expected, right? But if the actual argument is something complicated, especially if it makes a nested function call, how can that possibly be implemented? Given an arbitrary expression that calls some external function, how can assigning a value to that expression make sufficient changes in the runtime environment such that subsequently evaluating that expression will yield the expected value i.e. the value that had been assigned? Or is the default of passing two closures (GET and UPDATE) *only* if the actual-argument expression is simple enough that it's invertible, and in complicated cases only a GET closure is passed (or the UPDATE closure is simply a signal of a runtime error that you're not allowed to assign a value to a complicated expression)? IMO the "right" way to pass parameters that can be modified is to use "locatatives" as in the Lisp Machine. That converts the idea of a "place" (as used by SETF in Common Lisp) into a "first class citizen" which can be passed around and stored etc., compared to a SETF place which is merely a compiletime-macro trick to convert place-references in source code into direct calls to the appropriate accessor just above the place followed by specialized SETter call to do the act. A hack to emulate a locative in CL would be to pass a closure where the code to find the object directly containing the place, and any parameters needed to find that place, and the function needed to perform the act. Then the called function would need to know it's going to get such a thunk-like closure, but since it's expecting to modify one of its parameters anyway, that's reasonable. Sketch of implementation (two special cases): (defun make-thunk-cadr (topptr) (let* ((midptr (cdr topptr)) (getclo (make-getter-closure :PARENT midptr :GETTERFN #'car :PARMS nil)) (setclo (make-setter-closure :PARENT midptr :SETTERFN #'rplaca :PARMS nil))) (make-thunk getclo setclo)) (defun make-thunk-aref1 (topptr arrindex1) (let ((getclo (make-getter-closure :PARENT topptr :GETTERFN #'aref1 :PARMS (list arrindex1))) (setclo (make-setter-closure :PARENT midptr :SETTERFN #'setaref1 :PARMS (list arrindex1)))) (make-thunk getclo setclo)) (defun swap (thunk1 thunk2) (prog (tmp) (setq tmp (thunk-get thunk1)) (thunk-set thunk1 (thunk-get thunk2)) (thunk-set thunk2 tmp))) ;Definitions of make-getter-closure make-setter-closure make-thunk ; thunk-get thunk-set not shown because they depend on whether ; closures and thunks are implemented via tagged assoc lists or ; DEFSTRUCT structures or CLOS objects or whatever. But I made the ; call to the constructors explicit enough that it should be obvious ; what components are inside each type of object. Note that with ; CLOS objects, this could all be condensed to have a single CLOS ; object which is the thunk which has two methods GET and SET, no ; need to make closures for get and set separately, templates for ; those closures are made automatically when the CLOS class is ; defined, and closures are generated from those templates whenever ; a new CLOS thunk-object is made. Thus: ; ... (make-CLOS-thunk :PARENT topptr :GETTERFN #'aref1 :SETTERFN #'setaref1 ; :PARMS (list arrindex1)) ... ;Example that should actually work: (format t " arr: ~S~% ixs: ~S~%" arr ixs) arr: #'(3 5 7 11 13)) ixs: (2 4) (setq arr #'(3 5 7 11 13)) (setq ixs (list 2 4)) (setq thunkcadr (make-thunk-cadr ixs)) ;Locative to the 4 in ixs (setq thunkaref (make-thunk-aref1 arr (thunk-get thunkcadr))) ;Locative to the 11 in the array (swap thunkcadr thunkaref) (format t " arr: ~S~% ixs: ~S~%" arr ixs) arr: #'(3 5 7 4 13)) ixs: (2 11) I haven't implemented this. I'm just specifying what the behaviour should be and giving a sketch how it ought to be easily doable in Common Lisp. And I'm not going to implement it because I have no use for this way of coding, at least not currently or in the foreseeable future. <tmi> Generally my abstract data type is at a higher level where the caller doesn't know that a single place is going to need to be SETFed, so there's no point in getting a locative to work with. Instead there's some *kind* of update to do, and parameters to that *kind* of update; what really happens internally (one or more SETFs, or alternately re-build anything that changed and share what didn't change) doesn't need to be known by the caller. All the caller needs to know is generically whether the update is in-place or non-destructive. (If it's non-destructive, then the new edition of the data structure is one of the return values. If it's in-place, then there's no need to bother with setq of the new value, because my structures always have a header cell that has a tag for the intentional datatype, and that header cell always points to either the in-place-modified object or the latest-edition-of-object.) </tmi> <mtmi> I'd rather program in "paranoid" mode than in "risk shoot foot" mode. The CAR of each ADT object is a keyword identifying the intentional type of that object, and every function that operates on that intentional type first checks if the parameter really does have the expected CAR, just to make sure I didn't copy&paste some inappropriate function name in my code. Yeah, it takes extra CPU cycles to do that checking on every calls, but it sure saves me from shooting myself in the foot and having to spend an hour to find out how I did it before I can fix it. <mtmi> <emtmi> TMI = Too Much Information (actually YMMV, some readers might like it) MTMI = More of Too Much Information EMTMI = Even More of Too Much Information (only newbies need read) Credits to Babylon Five for the "I spy" game in the cargo hold. I spy something that starts with the letter B. Boxes! I spy something that starts with the letter M. More boxes! <emtmi> -- http://mail.python.org/mailman/listinfo/python-list