Version 1.3 of ai05s/ai05-0142-1.txt

Unformatted version of ai05s/ai05-0142-1.txt version 1.3
Other versions for file ai05s/ai05-0142-1.txt

!standard 6.5          09-02-14 AI05-0142-1/01
!class Amendment 09-02-14
!status work item 09-02-14
!status received 09-01-19
!priority Medium
!difficulty Medium
!subject Variable function results
!summary
(See proposal.)
!problem
Modifying a portion of a larger opaque object (such as a container) is not well-supported in Ada. The Ada.Containers packages provide a procedure Replace_Element for this purpose. But this procedure requires copying the element (potentially in both directions). That could be very expensive if the element is large.
The Ada.Containers packages also provide an procedure Update_Element for this purpose; this provides a writable object as a parameter to a subprogram passed into the procedure. This procedure avoids the need to copy the element, but it is hardly convenient to define a procedure for every component of the element that needs changing. The extra syntax needed obscures the real meaning of the program.
An option that was rejected for the Ada.Containers packages was to return an access to the element type. However, this is problematic as it is difficult to control the accessibility and lifetime of the returned access. If the element is removed from the container, the returned access could become dangling; continued use of the access would make the program erroneous. Moreover, the accessibility of the returned object (and thus what could be done with it) would depend on the actual implementation of the container. Bounded containers would typically only return access values with a very short lifetime, while unbounded containers would typically return access values with a much longer lifetime. Converting from an unbounded to bounded form could thus introduce new runtime errors - a serious maintenance hazard.
What really is needed is to be able to return a variable from a function, a variable view whose lifetime is defined to be exactly the same as that of the function call.
!proposal
Add an "in out" mode to a function result. This would have a meaning as close to "in out" parameter passing as possible. In particular, the mode would mean that a (view of) a variable is returned. The view would have the accessibility of the function call, and could not be used afterwards.
Similarly, the "in out" mode for a function return would use return-by-reference (return by-copy/result could be supported, see the !discussion).
An accessibility check would need to be made at the point of the return; anything that had a lifetime at least as long as the function call (including its parameters) could be returned, but not local objects.
!wording
** TBD **
!discussion
This proposal is very similar to the old return-by-reference functions of Ada 95, except that it is allowed for all kinds of types. The primary problems with them were that they were not allowed for all types.
The big advantage of this proposal is that any variable can be returned, without cluttering the syntax with access types and aliased declarations. Moreover, the resulting view can be directly assigned, selected, etc. Indeed, this proposal would give the illusion of a user-defined array indexing operation:
function Index (Obj : in out Container; I : in Natural) return Element_Type;
could be used (assuming Container is tagged) as
My_Container.Index(10) := Some_Element; and Some_Element := My_Container.Index(5);
The short lifetime of the variable view would prevent almost all dangling references. An attempt to take an 'Access of a component of the object would almost always fail (the object itself would not be defined as aliased so 'Access would not be allowed on it). Another task could deallocate the underlying object, and that would make using a returned variable erroneous (by existing rules). Similarly, renaming this variable view would allow other operations to be executed while the variable view still existed. For a container, this could allow a call to Delete the element, also leading to erroneous execution (if the element's memory was freed) or just junk (if the element was reused).
One solution to the latter problem would be to make renaming of such functions illegal, but that would be quite inconsistent with the rest of the language.
Note that if a container library only allowed this syntax in a tampering context (where a tampering context is declared by an object or a call-back as it currently is, making it scoped), then such a routine is safe.
Alternative syntaxes:
If we don't extend the language to allow "in out" parameters on functions, allowing them on the result would be really, really weird. But the capability would still be useful for returning parts of heap allocated objects (such as elements in an unbounded container) and for returning parts of access parameters (both named and anonymous). In that case, we'd need another syntax.
One possibility is:
function Element (Obj : in Container) return not constant Element_Type;
(Aside: for this syntax, we'd really rather use
function Element (Obj : in Container) return constant Element_Type; for our current meaning and function Element (Obj : in Container) return Element_Type; for our new feature as that would be consistent with the rest of the language. But that is way too incompatible.)
Another possibility is a new keyword:
function Element (Obj : in Container) return variable Element_Type;
Termination alternative:
An alternative to erroneousness for cases where the object whose view is returned ceases to exist while the function return is still usable would be to have some sort of call-back that occurs when the returned view ceases to exist. In this case, the accessor function itself can become a tampering context - setting the marker when called and clearing it when the view ceases to exist.
This model is consistent with by-copy returning of objects (defined consistently with parameter passing); in that case such a call-back to do the copying back to the original object would be required at the point that the variable view ceases to exist (the end of the master of the function call). It is only a small step to making that call-back something that can be used by the programmer.
This could take the form of an extended extended_return statement:
return defining_identifier : [aliased] return_subtype_indication [:= expression] [do
handled_sequence_of_statements
[at end handled_sequence_of_statements] end return];
The idea being that the "at end" statements are executed when the return object or view ceases to exist. Indeed, a capability like this for access returns (along with some way to control the accessibility of the result to the caller level) would be sufficient to solve the major problem of safely returning a value. But it seems too radical (and relatively expensive if not indicated in the profile of the function somehow) to make as an integral part of this proposal.
User-defined dereference:
Such a variable-returning function is essentially a user-defined dereference. So an additional feature is not really needed. But, if we wanted to, we could define one easily using this feature:
function "all" (Ptr_Like : in Private_Pointer) return in out Designated_Type; It could then be used in the normal way: P : Private_Pointer; ... P.all := <some aggregate>; Some_Procedure (P.all);
This would make it possible to define controlled types which worked exactly like access types (and presumably would use the finalization events to do appropriate storage management).
Acknowledgement: The basic idea for this feature came from a comp.lang.ada discussion, and from Dmitry Kazakov in particular. It's surely somewhat different than that initial idea; blame the author, not the idea, for any problems.
!examples
In the existing containers libraries, this would be used as something like:
function Modifiable_Element (Container : in out Vector; Position : in Cursor) return in out Element_Type;
Note that the name "Element" has already been used by the containers libraries, and it is fact was used for a function with a type-conforming profile, so using that name would be inadvisable. Moreover, the fact that the container is "in out" when it can be modified (a standard design feature of the containers libraries) suggests that a different name be used so that it is still possible to read "in" parameter containers.
This could be used (based on a recent example from comp.lang.ada):
with Ada.Containers.Vectors; procedure Check is package Integer_Vectors is new Ada.Containers.Vectors (Index_Type => Natural, Element_Type => Integer);
package Nested_Vectors is new Ada.Containers.Vectors (Index_Type => Natural, Element_Type => Integer_Vectors.Vector, "=" => Integer_Vectors."=");
IV : Integer_Vectors.Vector; NV : Nested_Vectors.Vector; begin IV.Append(42); NV.Append(IV); NV.Modifiable_Element(0).Append(43); end Check;
!ACATS test
!appendix

From: Bob Duff
Sent: Sunday, February 15, 2009  11:04 AM

> Add an "in out" mode to a function result. This would have a meaning 
> as close to "in out" parameter passing as possible. In particular, the 
> mode would mean that a (view of) a variable is returned. The view 
> would have the accessibility of the function call, and could not be used
> afterwards.

It would indeed be nice to solve this problem.  It would also be a nice side
effect to have a workaround for poeple who used return-by-ref in Ada 95, and
get bitten by the incompatibility.

(I realize I'm partly contradicting my earlier rant, but I really mean "if it's
not complicated"!)

A couple of other ideas that we might want to consider, for solving the same
problem:

Limited access types.  These would be restricted in some way such that you can't
(easily) save away a copy and then have it become dangling.  This is similar
to Randy and Dmitry's proposal, but makes the reference semantics explicit.
I'm not 100% sure whether that's an advantage or a disadvantage.
But I never likes Ada 95's return-by-ref feature, because I don't like implicit
reference semantics.  (I just wish .all was spelled as it is in Pascal!)

By the way, I handled a customer report a few days ago, from someone who was
using Ada 95's return-by-ref precisely for this reason.  They were asking how
to do the same thing in Ada 2005.

A second idea is to have syntactic sugar for passing subprograms as parameters.
If we make it sweet enough, Update_Element calls could be made readable.
We'd have to allow the actual procedure to be anonymous, as in Lisp lambdas,
and get rid of most boilerplate verbiage.

****************************************************************

From: Christoph Grein
Sent: Monday, February 16, 2009  3:25 AM

This report Bob mentions was about my package Safe_Pointers which you can find
at http://www.adaic.org/learn/ (a somewhat dated copy). For limited types, the
"function Value (...) return Object" (return-by-reference) is illegal now and
has to be replaced by "return access Object" or some such, which completely
undermines the idea of a safe pointer.

I know the Ada95
  X: Object renames Value (Ptr);
may also quietly become dangling but the scope of X is much smaller than that
of an access object that may be stored away.

I would love to see a function returning a kind of limited pointer that cannot
be stored away, but can be dereferenced or used as a prefix for component
manipulation like e.g.

function Value (P: Safe_Pointer) return limited access Object; Value(Ptr).X := 5.0;

****************************************************************

From: Randy Brukardt
Sent: Monday, February 16, 2009  8:04 PM

> I would love to see a function returning a kind of limited pointer 
> that cannot be stored away, but can be dereferenced or used as a 
> prefix for component manipulation like e.g.

This idea has been raised before, but the idea of limited elementary types
gives everyone hives.

That's why I've proposed two ways to solve this problem, the proposal that you're
commenting on and also the "call-level accessibility" that Bob didn't understand
in his rant.

The idea of the latter is that the accessibility of the returned point in something like:

     function Value (P : Safe_Pointer) return access Object with Call_Level_Accessibility;

is that of the function call. Thus it can't be assigned into any existing access object
(it could be renamed); the intent is that it is *only* for dereference.

I prefer getting rid of the "access" altogether (if we have pass-by-reference why shouldn't
we have return-by-reference??), but either scheme will work. I'll do everything I can to
ensure one or the other is adopted.

****************************************************************

From: Randy Brukardt
Sent: Monday, February 16, 2009  11:13 PM

...
> A second idea is to have syntactic sugar for passing subprograms as 
> parameters.
> If we make it sweet enough, Update_Element calls could be made 
> readable.
> We'd have to allow the actual procedure to be anonymous, as in Lisp 
> lambdas, and get rid of most boilerplate verbiage.

You've mentioned this before, but I don't see how it could work. If you require
writing enough stuff so that the result makes sense to a casual reader, it's still
going to be a big glob of text (you can't get rid of the parameter decls or the
begin and end). Sticking that in the middle of a call doesn't sound pleasant. I
suppose you could make the parameters implicit, but then readability is going
to be severely impacted, because you'd have objects that are referenced by never
declared anywhere in the text (at least nowhere obvious). That doesn't sound
very Ada-like.

****************************************************************

From: Bob Duff
Sent: Tuesday, February 17, 2009  8:05 AM

> You've mentioned this before, but I don't see how it could work.

I'm not sure, either.  But your proposal elsewhere for iterator syntax
is sort of the same idea, and could perhaps provide some ideas.

****************************************************************


Questions? Ask the ACAA Technical Agent