Path: utzoo!utgpu!news-server.csri.toronto.edu!mailrus!wuarchive!uunet!microsoft!bobatk From: bobatk@microsoft.UUCP (Bob ATKINSON) Newsgroups: comp.object Subject: Re: code blocks (aka closures) Message-ID: <55703@microsoft.UUCP> Date: 6 Jul 90 23:43:53 GMT References: <5305@stpstn.UUCP> <11002@alice.UUCP> <1990Jul3.143743.22901@cbnewsc.att.com> <55700@microsoft.UUCP> Reply-To: bobatk@microsoft.UUCP (Bob Atkinson) Organization: Microsoft Corp., Redmond WA Lines: 149 >Peter Deutsch writes: >> [paraphrased by rga] "C today lets us take pointers to local variables, >> even though they are `dangerous'. One can imagine a similar form of >> `dangerous' closures". It seems to me that these form of closures would suffice for a significant fraction of the situations in which I find myself using closures in Smalltalk, upwards of 85% or so. Most of these are involved in enumeration of one form or another, and do *not* require the closure to outlive the activation record of its outer scope. I would find a `dangerous' closure construct extremely useful. But, you say, we already have a mechanism in C++ that supports these situations: Enumerator classes. To wit: // The generic enumerator class, from which // all enumerators will derive. // class ENUMERATOR { int (*pfnNext)(): // Call to get next element void (*pfnEnd)(); // Call at premature loop termination public: int Next() { return pfnNext(); } void End(); { pfnEnd(); } }; // An enumerator for enumerating in forward order // the elements of a COLL (collection) class. // class ENM_COLL: public ENUMERATOR { // Insert necessary private enumeration state here. public: ELE *pelement; void Init(COLL *pcoll); // Implemenation not shown but it will set // pfnNext and pfnEnd. }; // ...... COLL *pcoll; ENM_COLL enmColl; enmColl.Init(pcoll); while(enmColl.Next()) { // ... // Do something with enmColl.pelement as desired // ... if (someCondition) { enmColl.End(); break; } } (Person's more proficient with C++ most certainly could improve on the coding style presented here. Asking the COLL itself for an enumerator is one example.) Some things are of note: 1) The enumerator class essentially construct a stack frame by hand: if we had `dangerous' closures, what is found in the enumerator would instead be kept in the activation record of COLL::Do(/*sometype*/ pfn). This has always seemed a little strange to me: why should I have to build by hand that which the system build _for_ me most of the time? 2) Each different manner of enumerating some aspect of an object requires a different enumerator class, since a different "stack frame" will be required. Complex objects have several properties which one might want to enumerate. For example, a word processing document might provide methods for enumerating the sections, paragraphs, words, tables, pictures, headers, footers, misspelled words, etc., that it contains. A Hypercard card might provide enumerations of its buttons, its fields, or the available fonts. A Dictionary needs to enumerate both its keys and values. In the absence of programming environment support, a proponderence (sp?) of auxilliary classes burdens the programmer with the task of documenting which classes are important, and which are relatively minor. A natural way to attempt to understand a unfamiliar piece of code is first to ask "what classes are there?". If it they all seem of equal importance, then their sheer number may be overwhelming. The important classes must be documented to stand out from the minor ones. Lack of support for such disctinctions in Smalltalk-80 I believe is one of the causes of the general feeling that creating a new class is a "big event" in that environment. 3) The call to ENUMERATOR::End() is always needed at premature loop termination, in case the object being enumerated is maintaining some special state (such as "pending deletions", for instance). In Smalltalk-80 V2.5, I would write: do: aClosure [self noteEnumerationBeginning. 1 to: self size: do: [:index | aClosure value: (self at: index)]. ] valueNowOrOnUnwindDo: [self noteEnumerationEnding]. and in a client: coll do: [:each | "..." "Do something with each as desired" "..." someCondition ifTrue: [^self]]. Notice that the client need not worry that the _implemenation_ of do: requires special handling on loop termination. When closures are used, abormal loop termination corresponds _precisely_ to an early return from the "Do" procedure (via a non-local return in this Smalltalk example, and probably most often via a goto or a break in C++). "valueNowOrOnUnwindDo:" provides a hook that gets called at early (or non-early) return. Such a mechanism, together with closures, would permit us to write C++ code that truly hid from clients the implemenation details of enumeration. I strongly such suspect that such a "return hook" could also be used by the system to implement the "walk the stack, call the destructor" part of an exception handling mechanism. Food for thought... As always, comments and thoughts are most welcome. Bob Atkinson Microsoft