Path: utzoo!utgpu!news-server.csri.toronto.edu!cs.utexas.edu!usc!elroy.jpl.nasa.gov!decwrl!pa.dec.com!src.dec.com!Mike_Spreitzer.PARC@xerox.com From: Mike_Spreitzer.PARC@xerox.com Newsgroups: comp.lang.modula3 Subject: Re: Closures in m3 Message-ID: <91Feb19.080826pst.16173@alpha.xerox.com> Date: 19 Feb 91 16:09:28 GMT Lines: 79 In-Reply-To: "<7006@rossignol.Princeton.EDU>" To: nr@elan.princeton.edu X-Ns-Transport-Id: 0000AA008EE7FED52B35 Cc: m3, Mike_Spreitzer.PARC@xerox.com An even simpler approach would be to use procedures without the REFANY argument, relying on the ability to make local procedures as the mechanism for binding to other data. For example, List could be changed to say: TYPE WalkProc = PROCEDURE (element: REFANY) RAISES {}; (* The type of procedures for walking over lists. *) PROCEDURE Walk( l: T; p: WalkProc ); (* Applies p to each of the elements of l in turn. The only exceptions raised are those possibly raised by p. *) A client that sums the element of a list might then look like this: PROCEDURE SumIt( it: List.T ) : INTEGER = VAR sum: INTEGER; PROCEDURE AddElt ( element: REFANY ) = BEGIN (* code to add the element goes here *) END AddElt; BEGIN sum := 0; List.Walk(it, AddElt); RETURN sum; END SumIt; Since the calls on enumerations are from procedure bodies already, there is always a plausible place to put the local procedure declaration. Designing enumeration interfaces raises the issue of concurrency. The above clearly works if the enumeration is sequential and List.Walk does not return untill all the "callbacks" have been made. If enumeration is not sequential, the interface ought to say so (in M3, this can only be done with English comments), and the client has to prepare for it by doing the necessary synchronization, if any, in the local procedure. If List.Walk might return before all the callbacks have been done, List.Walk will have to also return a cookie that the client can use to synchronize with the completion of the enumeration. All three schemes (extra REFANY parameter, closure objects, local procedures) could be made to work if such considerations are made [but the local procedure scheme needs an additional change: the ability to assign local procedures to procedure variables; this is currently forbidden, but no more unsafe than dereferencing UNTRACED references --- so I don't see why it shouldn't be equally possible.] Proceeding further along the local procedure direction, we get the elegance of CLU iterators. Imagine that M3 has a syntax for procedure literals. For example, suppose that LAMBDA sig = Block is the syntax for procedure literals. Then the SumIt could look like this: PROCEDURE SumIt( it: List.T ) : INTEGER = VAR sum: INTEGER; BEGIN sum := 0; List.Walk(it, LAMBDA (element: REFANY) = BEGIN (* add element *) END); RETURN sum; END SumIt; Thus, programmers can create their own kinds of looping constructs, and use only requires a little syntactic boilerplate. That boilerplate could be further minimized if the syntax Block were allowed for procedure literals when the context of the literal provides the type (ie, signature) for the procedure. With that proviso, SumIt could look like this: PROCEDURE SumIt( it: List.T ) : INTEGER = VAR sum: INTEGER; BEGIN sum := 0; List.Walk(it, BEGIN (* code to add the element goes here *) END); RETURN sum; END SumIt; The latter gets too syntactically fragile for my taste when the Block has declarations. But then, declarations before the BEGIN rather than after tastes inferior to me. Adding a new kind of bracket to make blocks into procedure literals is possible, but that's more complex than I'd like to see. Mike