From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00 autolearn=ham autolearn_force=no version=3.4.4 X-Google-Thread: 103376,21960280f1d61e84 X-Google-Attributes: gid103376,public X-Google-Language: ENGLISH,ASCII-7-bit Newsgroups: comp.lang.ada Subject: Re: in defense of GC References: <1169531612.200010.153120@38g2000cwa.googlegroups.com> <1mahvxskejxe1$.tx7bjdqyo2oj$.dlg@40tude.net> <2tfy9vgph3.fsf@hod.lan.m-e-leypold.de> <1g7m33bys8v4p.6p9cpsh3k031$.dlg@40tude.net> <14hm72xd3b0bq$.axktv523vay8$.dlg@40tude.net> <4zwt33xm4b.fsf@hod.lan.m-e-leypold.de> <1j7neot6h1udi$.14vp2aos6z9l8.dlg@40tude.net> <1pzx3y7d2pide.y744copm0ejb$.dlg@40tude.net> <2pabzt1kzw.fsf@hod.lan.m-e-leypold.de> From: Markus E Leypold Organization: N/A Date: Tue, 06 Feb 2007 02:06:19 +0100 Message-ID: User-Agent: Some cool user agent (SCUG) Cancel-Lock: sha1:6KVaOOxV8rOna006guW1xSJG+zA= MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii NNTP-Posting-Host: 88.74.38.71 X-Trace: news.arcor-ip.de 1170723675 88.74.38.71 (6 Feb 2007 02:01:15 +0200) X-Complaints-To: abuse@arcor-ip.de Path: g2news2.google.com!news3.google.com!border1.nntp.dca.giganews.com!nntp.giganews.com!newsfeed00.sul.t-online.de!newsfeed01.sul.t-online.de!t-online.de!newsfeed.arcor-ip.de!news.arcor-ip.de!not-for-mail Xref: g2news2.google.com comp.lang.ada:9007 Date: 2007-02-06T02:06:19+01:00 List-Id: Robert A Duff writes: >> So in (impure) FP both cases are possible and if we go back to Ada >> syntax from that: Why not use the constant keyword to distinguish the >> cases? > > Because in Ada I want local variables (local in visibility and local and > lifetime). I think the Ada equivalent of OCaml's non-functional > "let n = ref (complicated k)" would be: > > type Ref_Integer is access Integer; > N: constant Ref_Integer := new Integer'(Complicated_Function(K)); > > That makes it clear that the integer variable we're talking about is an > object allocated on the heap and therefore has who-knows-what lifetime. > I have no problem with forming a closure that can see N (a constant) > and passing that closure outward. > > My point is: I want to understand the lifetime of a variable (Ada term) > by looking at its declaration. I don't care about the lifetimes of > constants/values -- they can be copied willy-nilly anyway. I agree with the last part of the last sentence, but your handling of mutable data strikes me as complicated: Variables are already namens for mutable storage (that is different in ML and why we need an explicti ref there). So N: Integer vs. N: constant Integer should be quite enough. >>> In the former case, we're returning a function that looks at some local >>> value (the value of N). In this case, you comment about returning >>> integer values is quite correct: >>> >>>>> Closure don't "make things global". They do it as much as returning an >>>>> Integer, makes, GOD FORBID!, a value global (don't return Integers >>>>> people, that makes a value global and we all know, global is bad -- >>>>> how nonsensical is that?). >>> >>> Right. What's the big deal? Returning a function that looks at integer >>> values is no worse than returning integer values. Integer values live >>> forever, and the value of N is certainly not tied to the lifetime of N. >> >> >>> But in the latter, case, the return of Stepper is causing the lifetime >>> of an integer VARIABLE (i.e. mutable) to be longer than one might >>> suspect for a local variable of Make_A_Stepper. That seems like a >>> problem to me. >> >> It's still only encapsulated _state_, the visibility of indentifiers >> (scope) is not extended by that. > > Right. We're talking about lifetimes. Some folks in this thread may > have said "scope" when they meant "lifetime". Yes, that was the underlying reason dfor sime mixups. But as I said: We've already indefinitely long living storage by the fact that we have 'new' and can return objects (which contain state). > >>...Outsiders can't look into the >> closure. And we already have encapsulate state like this, in tagged >> objects. Closure differ from objects only in 2 points: (1) they have >> only one method (apply) and (2) they can take (implicitely) values >> from their whole environment at the place of instantiation. >> >> Indeed the more I think about it, a bit compiler support (which knows >> which parts of the environment are actually used in the closures >> body), a new keyword (closure to distinguis them from ordinary >> procedures) and all the mechanisms already present in tagged types >> should be quite enough implement closure in Ada. (Well, OK, there are >> problems with sharing N in the non-constant case with other closures >> and other procedure which can see N, but in the constant case (which >> is perhaps in the presence of object the mor important one), 'constant >> N' can be just copied into a implementation object of the type >> >> tagged record >> N : Integer; >> end record; BTW. I take that back. It's not as easy as that to tack really useful closures on top of a stack oriented language implementation. >> >>> You (Markus) seem to be an advocate of (possibly-upward) closures. >> >> Certainly. As I said in another post of the recent article storm, I >> _think_ (think, mind you, no hard data) that if too much thinks >> (narrowing of type w/o RTTI during method invocation, scope of access >> types and passing procedural parameters can only be "done downwards", >> some desirable structures become impossible. >> >>> And also an advocate of function programming. >> >> 'functional'. :-). > > Sorry, that was a typo. I meant "functional". I know. Therefore the ":-)". > >>> So how would you like a rule that says the former example is OK, but >>> the latter is illegal? >> >> I'd weep silently :-). >> >>> (That is, upward closures are allowed only when you're doing functional >>> (no side-effects) programming). >> >> >>> Note that the latter is not functional -- a call to the function that >>> Make_A_Stepper returns modifies N, which is the key difference! >> >> Yes. That is so. But Ada is not functional any way and impure closures >> have their value. > > This is the key point I'm interested in: why do impure outward closures > have value? > We can emulate that via tagged types, as you say: Because ideally they can capture their whole environment e.g. when returning functions that return functions that return functions. Of course for any specific case that can be cumbersomely emulated by using tagged objects: But that requires very specialized solutions in every single case. It feels -- wrong? Why encaspualting mutable state? Well -- i.e. to produce time stampers and similar functional objects. I admit, perhaps, just copying to the heap all the "external values" a "closure" refers to at instantiation time will have the desired effect (mostly). This still leaves the possibility to encapsulate mutable state by having a heap reference (as you did above), Perhaps that will work. I do not remember any important cases where state sharing between different closures really was an important consideration. "closure" in the following code fragment will thus just be syntactic sugar for deriving a class and instantiating some object from it: type Stepper_T is closure ( I : Integer ) return Integer; -- in reality a cloaked tagged type function Make_Stepper ( K : Integer ) return Stepper_T N : Integer := Step_Width(K); closure Stepper ( I : integer ) return Integer is begin -- derive a new tagged type and body becomes method, instantiate once return I + N; end; is begin return Stepper; end; I try to sketch what the underlying implementation would be: type Stepper_T is abstract tagged null record; procedure Apply( S : Stepper_T ) return Integer is abstract; function Make_Stepper ( K : Integer ) return Stepper_T N : Integer := Step_Width(K); type Concrete_Stepper is new Stepper_T with record N : Integer; -- This identifier is produced by the compiler from N in Stepper body because it refers to a definition outside the body of Stepper end record; procedure Apply( S : Concrete_Stepper ) return Integer is return I + S.N; -- N as reference outside stepper body is rewritten to S.N. end; Stepper : Concrete_Stepper'( N => N ); is begin return Stepper; end; Having a shortcut syntax as follows would be also nice: type Stepper_T is abstract tagged null record; function Make_Stepper ( K : Integer ) return Stepper_T N : Integer := Step_Width(K); is begin return closure ( I : integer ) return Integer is return N+I; end; (This looks a bit strange, but what I want to say is, that the closure name is actually not important and perhaps the begin/end block is a bit nois in some cases. But YMMV.) Even shorter: type Stepper_T is abstract tagged null record; function Make_Stepper ( K : Integer ) return Stepper_T N : Integer := Step_Width(K); is return closure ( I : integer ) return Integer is return N+I; >>...Nonetheless: Since the encapsulation of mutable >> state can be done in tagged types forbidding the latter would probably >> not hurt as much as in languages w/o OO. Still, that requires further >> analysis of the most frequent use cases. > > And it seems to me there's some value in knowing that a local variable > of a procedure has local lifetime. It has a local lifetime: Next call into the procedure could create a new one, which of course could be captured by another closure. It would not be like "static" variables in C! > And if it doesn't, the extra syntactic baggage of tagged types or > access types or whatever seems like a benefit (i.e. warning: this > thing lives longer). I understand your approach. It would go well with a "copy all relevant data to the closure" approach (as sketched above), so basically, yes (I hope I really understood what I'm agreeing to). Regards -- Markus