comp.lang.ada
 help / color / mirror / Atom feed
* Controlled types and exception safety
@ 2005-11-30 13:57 Maciej Sobczak
  2005-11-30 15:06 ` Dmitry A. Kazakov
                   ` (2 more replies)
  0 siblings, 3 replies; 37+ messages in thread
From: Maciej Sobczak @ 2005-11-30 13:57 UTC (permalink / raw)


Hi,

I try to understad how the Controlled types work and I spotted one 
"small issue" that makes it difficult to write exception-safe code.

The "exception-safe" means that code behaves "correctly" in the presence 
of exceptions, for some chosen definition of "correctly".
In C++ we define the following levels of exception-safety:
- level 0 (no guarantee) - in the presence of exception, anything can 
happen, memory may become corrupted, data structures may become 
completely mangled, etc.
- level 1 (basic guarantee) - in the presence of exception, no resources 
are leaked and objects are in a coherent, but not necessarily 
predictable state.
- level 2 (strong guarantee) - in the presence of exception, the program 
state (to the relevant extent) remains unchanged. This is similar to the 
commit-or-rollback semantics known from databases.
- level 3 (nothrow guarantee) - the code simply guarantees that there 
are no exceptions.

Why is this classification useful? Let's say that I have an abstract 
data type that implements some data structure - a stack, for example. I 
can classify the stack's operations by assigning them any of the above 
four levels, so that I know what can be expected when an exception is 
thrown for any reason (like inability to allocate more memory, or 
alike). For example, if the Push method of the stack gives me the strong 
guarantee (level 2 above), then I *know* that by calling this method 
either the new element will be appended to the stack, or the stack will 
remain unchanged, so that even if the exception is thrown, I don't have 
to worry about the stack's internal consistency.
This is useful.

This is useful also in assignment operations. Since stack can be a 
dynamic data structure, assigning one stack object to another may 
involve destroying one existing data structure *and* creating a new one 
(a copy) in its place. Similarly, the quality implementation should 
provide the strong guarantee, so that I *know* that either the stack was 
properly copied, or there was a problem during assignment and an 
exception was thrown, but nothing changed in any of the objects involved.

Let's say that I want to write a stack in Ada. Making it a Controlled 
type seems to be a good idea, so that we have hooks for initialization, 
adjusting and finalization. Let's say that I have two stack objects, X 
and Y:

X, Y : Stack;

These objects were populated with some data, so that each of them 
manages its own internal dynamic data structure.
Now, I do this:

X := Y;

and the following happens (this is what I understand, please correct me 
if I'm wrong):

1. X is finalized. This allows me to clean up (free) its internal data.
2. Y is *shallow-copied* to X, so that in effect X and Y share their state.
3. X is adjusted. This allows me to duplicate its internal structure so 
that it becomes independent from Y.

later:
4. Both X and Y are finalized. This allows me to clean up (free) their 
resources.

For everything to work correctly it's important that two separate stack 
objects *never* share their internal dynamic data structure, otherwise 
bad things can happen. It would be also fine not to leak memory.

Now, the interesting part: let's say that during adjustment (3.) some 
error happened (like low memory condition or whatever) that resulted in 
raising an exception (note: this exception might be actually raisen not 
by the stack code, but by the assignment operation of the stack 
elements, even somewhere in the middle of this process).
Bad things will happen in subsequent finalization of those objects, 
unless I handle it by cleaning up everything that I already managed to 
duplicate (but still, this leaves me with the empty stack).


I think that the inherent problem comes from the fact that the 
finalization of X was forced *before* its adjustment.
The canonical C++ way is to *first* make a copy of new value (because 
this is when errors might occur, so that even if they occur, there was 
no change in the destination object) and *then* inject the duplicate 
into the destination object, getting rid of its old state (and this is 
assumed to be nothrow).

The "Ada way" looks like selling the house *before* looking for the new one.

What do you do to avoid surprises?


-- 
Maciej Sobczak : http://www.msobczak.com/
Programming    : http://www.msobczak.com/prog/



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-11-30 13:57 Controlled types and exception safety Maciej Sobczak
@ 2005-11-30 15:06 ` Dmitry A. Kazakov
  2005-11-30 16:19   ` Maciej Sobczak
  2005-11-30 17:46 ` Jean-Pierre Rosen
  2005-11-30 21:02 ` Jeffrey R. Carter
  2 siblings, 1 reply; 37+ messages in thread
From: Dmitry A. Kazakov @ 2005-11-30 15:06 UTC (permalink / raw)


On Wed, 30 Nov 2005 14:57:07 +0100, Maciej Sobczak wrote:

> Let's say that I want to write a stack in Ada. Making it a Controlled 
> type seems to be a good idea, so that we have hooks for initialization, 
> adjusting and finalization. Let's say that I have two stack objects, X 
> and Y:
> 
> X, Y : Stack;
> 
> These objects were populated with some data, so that each of them 
> manages its own internal dynamic data structure.
> Now, I do this:
> 
> X := Y;
> 
> and the following happens (this is what I understand, please correct me 
> if I'm wrong):
> 
> 1. X is finalized. This allows me to clean up (free) its internal data.
> 2. Y is *shallow-copied* to X, so that in effect X and Y share their state.
> 3. X is adjusted. This allows me to duplicate its internal structure so 
> that it becomes independent from Y.
> 
> later:
> 4. Both X and Y are finalized. This allows me to clean up (free) their 
> resources.
> 
> For everything to work correctly it's important that two separate stack 
> objects *never* share their internal dynamic data structure, otherwise 
> bad things can happen. It would be also fine not to leak memory.
> 
> Now, the interesting part: let's say that during adjustment (3.) some 
> error happened (like low memory condition or whatever) that resulted in 
> raising an exception

ARM 7.6.1 reads: "It is a bounded error for a call on Finalize or Adjust to
propagate an exception.
[...]
For an Adjust invoked as part of an assignment operation, any other
adjustments due to be performed are performed, and then Program_Error is
raised."

> I think that the inherent problem comes from the fact that the 
> finalization of X was forced *before* its adjustment.
> The canonical C++ way is to *first* make a copy of new value (because 
> this is when errors might occur, so that even if they occur, there was 
> no change in the destination object) and *then* inject the duplicate 
> into the destination object, getting rid of its old state (and this is 
> assumed to be nothrow).

Here the semantics of "copy", "inject", "duplicate" is ill-defined. In
general, you can copy a set of bits, but you cannot an object without
defining it in the terms copy-constructor. In Ada's case copy-constructor
is defined as Bitwise copy + Adjust. It is an atomic operation. Which is
equivalently means that in general case you cannot define any reasonable
semantics for its partial completion.

> The "Ada way" looks like selling the house *before* looking for the new one.
> 
> What do you do to avoid surprises?

Don't let exceptions propagate.

-- 
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-11-30 15:06 ` Dmitry A. Kazakov
@ 2005-11-30 16:19   ` Maciej Sobczak
  2005-12-01  0:05     ` Stephen Leake
  2005-12-01  9:21     ` Dmitry A. Kazakov
  0 siblings, 2 replies; 37+ messages in thread
From: Maciej Sobczak @ 2005-11-30 16:19 UTC (permalink / raw)


Dmitry A. Kazakov wrote:

>>Now, the interesting part: let's say that during adjustment (3.) some 
>>error happened (like low memory condition or whatever) that resulted in 
>>raising an exception
> 
> ARM 7.6.1 reads: "It is a bounded error for a call on Finalize or Adjust to
> propagate an exception.
> [...]
> For an Adjust invoked as part of an assignment operation, any other
> adjustments due to be performed are performed, and then Program_Error is
> raised."

OK, that brings some light, but does not solve my problem. :)

>>The canonical C++ way is to *first* make a copy of new value (because 
>>this is when errors might occur, so that even if they occur, there was 
>>no change in the destination object) and *then* inject the duplicate 
>>into the destination object, getting rid of its old state (and this is 
>>assumed to be nothrow).
> 
> Here the semantics of "copy", "inject", "duplicate" is ill-defined. In
> general, you can copy a set of bits, but you cannot an object without
> defining it in the terms copy-constructor.

When I said "copy" above (C++), I meant create a new object as a copy. 
This involves copy constructor. The point is that this new object is 
*separate* from the destination object (from what's on the left side of 
assignment operator), so even if there are errors, they do not influence 
any of the two objects which were originally involved. After this new 
helper object is constructed (which means: *successfully* constructed), 
it's "injected" in the destination object by means of swapping the bowels.
It looks like this:

class Stack
{
public:
     // other stuff, including copy constructor
     // ...

     // assignment operator with strong guarantee
     void operator=(const Stack &other)
     {
         Stack temp(other);  // this can fail, but that does not
                             // influence the "this" object

         swap(other);        // exchange of bowels (cannot fail)

                             // temp is cleaned up (cannot fail)
     }

private:
     // bowels
     // ...

     void swap(Stack &other)
     {
         // exchange bowels with the other Stack
         // this never fails if bowels involve only
         // fundamental types or components that
         // themselves have nothrow swap
     }
};

This idiom is very effective.

> In Ada's case copy-constructor
> is defined as Bitwise copy + Adjust.

The assignment is defined as Finalize + Bitwise copy + Adjust.
And it's the fact that Finalize comes first that bothers me.

> Which is
> equivalently means that in general case you cannot define any reasonable
> semantics for its partial completion.

Note that in the example above there is no "partial completion". On the 
contrary - either the operation completes successfully or it fails 
*wihout* modifying anything. Moreover, the scheme does not force me to 
ignore the error nor anything like this, I can let it go to the place 
where there's enough context to really handle it.

>>What do you do to avoid surprises?
> 
> Don't let exceptions propagate.

What do you mean by "don't propagate"? What if there is an exception 
that was raised by the run-time (like low memory condition) in the 
middle of adjusting the whole stack? What should I do with the part that 
was aleady adjusted (duplicated)? What should I do with the part that 
was not yet adjusted? Should I clean up what's already done and leave 
the destination stack as empty and shut the exception up, thus 
preventing the higer-level code from properly handling it?

Is it possible to have assignment with strong exception guarantee?


-- 
Maciej Sobczak : http://www.msobczak.com/
Programming    : http://www.msobczak.com/prog/



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-11-30 13:57 Controlled types and exception safety Maciej Sobczak
  2005-11-30 15:06 ` Dmitry A. Kazakov
@ 2005-11-30 17:46 ` Jean-Pierre Rosen
  2005-11-30 21:02 ` Jeffrey R. Carter
  2 siblings, 0 replies; 37+ messages in thread
From: Jean-Pierre Rosen @ 2005-11-30 17:46 UTC (permalink / raw)


Maciej Sobczak a �crit :

> 1. X is finalized. This allows me to clean up (free) its internal data.
> 2. Y is *shallow-copied* to X, so that in effect X and Y share their state.
> 3. X is adjusted. This allows me to duplicate its internal structure so 
> that it becomes independent from Y.
This is the simplistic view, which is sufficient in most cases.
The truth is slightly more complicated, see  7.6(11)

> I think that the inherent problem comes from the fact that the 
> finalization of X was forced *before* its adjustment.
> The canonical C++ way is to *first* make a copy of new value (because 
> this is when errors might occur, so that even if they occur, there was 
> no change in the destination object) and *then* inject the duplicate 
> into the destination object, getting rid of its old state (and this is 
> assumed to be nothrow).
This is the way it actually works in Ada, unless the compiler proves 
that the intermediate object can be eliminated. In your case, it cannot.

-- 
---------------------------------------------------------
            J-P. Rosen (rosen@adalog.fr)
Visit Adalog's web site at http://www.adalog.fr



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-11-30 13:57 Controlled types and exception safety Maciej Sobczak
  2005-11-30 15:06 ` Dmitry A. Kazakov
  2005-11-30 17:46 ` Jean-Pierre Rosen
@ 2005-11-30 21:02 ` Jeffrey R. Carter
  2005-11-30 22:06   ` Björn Persson
  2005-12-06 11:41   ` Peter C. Chapin
  2 siblings, 2 replies; 37+ messages in thread
From: Jeffrey R. Carter @ 2005-11-30 21:02 UTC (permalink / raw)


Maciej Sobczak wrote:

> 1. X is finalized. This allows me to clean up (free) its internal data.
> 2. Y is *shallow-copied* to X, so that in effect X and Y share their state.
> 3. X is adjusted. This allows me to duplicate its internal structure so 
> that it becomes independent from Y.

 From ARM 7.6:

  For an assignment_statement, after the name and expression have been 
evaluated, and any conversion (including constraint checking) has been done, an 
anonymous object is created, and the value is assigned into it; that is, the 
assignment operation is applied. (Assignment includes value adjustment.) The 
target of the assignment_statement is then finalized. The value of the anonymous 
object is then assigned into the target of the assignment_statement. Finally, 
the anonymous object is finalized. As explained below, the implementation may 
eliminate the intermediate anonymous object, so this description subsumes the 
one given in 5.2, ``Assignment Statements''.

So really:

1. An intermediate object of the type is created.
2. Y's bit pattern is copied into the intermediate object.
3. The intermediate object is adjusted.
4. X is finalized.
5. The intermediate object's bit pattern is copied into X.
6. X is adjusted.
7. The intermediate object is finalized.

-- 
Jeff Carter
"Crucifixion's a doddle."
Monty Python's Life of Brian
82



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-11-30 21:02 ` Jeffrey R. Carter
@ 2005-11-30 22:06   ` Björn Persson
  2005-11-30 23:52     ` Randy Brukardt
  2005-12-01  5:26     ` Jeffrey R. Carter
  2005-12-06 11:41   ` Peter C. Chapin
  1 sibling, 2 replies; 37+ messages in thread
From: Björn Persson @ 2005-11-30 22:06 UTC (permalink / raw)


Jeffrey R. Carter wrote:
> 1. An intermediate object of the type is created.
> 2. Y's bit pattern is copied into the intermediate object.
> 3. The intermediate object is adjusted.
> 4. X is finalized.
> 5. The intermediate object's bit pattern is copied into X.
> 6. X is adjusted.
> 7. The intermediate object is finalized.

Can't Maciej's concerns be applied to step 6? What to do about 
exceptions that happen while the new X is being adjusted, after the old 
X has been finalized?

-- 
Bj�rn Persson                              PGP key A88682FD
                    omb jor ers @sv ge.
                    r o.b n.p son eri nu



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-11-30 22:06   ` Björn Persson
@ 2005-11-30 23:52     ` Randy Brukardt
  2005-12-01  5:26     ` Jeffrey R. Carter
  1 sibling, 0 replies; 37+ messages in thread
From: Randy Brukardt @ 2005-11-30 23:52 UTC (permalink / raw)


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain, Size: 1685 bytes --]

"Bj�rn Persson" <spam-away@nowhere.nil> wrote in message
news:4opjf.151627$dP1.509433@newsc.telia.net...
> Jeffrey R. Carter wrote:
> > 1. An intermediate object of the type is created.
> > 2. Y's bit pattern is copied into the intermediate object.
> > 3. The intermediate object is adjusted.
> > 4. X is finalized.
> > 5. The intermediate object's bit pattern is copied into X.
> > 6. X is adjusted.
> > 7. The intermediate object is finalized.
>
> Can't Maciej's concerns be applied to step 6? What to do about
> exceptions that happen while the new X is being adjusted, after the old
> X has been finalized?

Sure, but it's a bug to let Finalize or Adjust propagate an exception. If
they do, the only reasonable assumption is that the object is corrupted. The
language bends over backwards to insure that a failure of one of these
operations for an object does not corrupt any other object (or component),
which is a strong guarantee in itself.

In just plain old (no controlled types around):
   A := B;
the raising of an exception during the assignment leaves A abnormal if A is
composite. In other words, Ada says that objects that are being assigned are
corrupted by an exception.

The solution is to not allow exceptions to be raised by Adjust. Yes, that's
not completely practical, because of Storage_Error, but even there you
should handle the exception and do what you can to prevent corruption of the
object. (Claw leaves the object invalid in this case, so future operations
on it, other than recreating it, will fail.) And this also suggests that you
should try to avoid allocating memory in Adjust (not always possible, of
course).

                       Randy.






^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-11-30 16:19   ` Maciej Sobczak
@ 2005-12-01  0:05     ` Stephen Leake
  2005-12-01  9:21     ` Dmitry A. Kazakov
  1 sibling, 0 replies; 37+ messages in thread
From: Stephen Leake @ 2005-12-01  0:05 UTC (permalink / raw)


Maciej Sobczak <no.spam@no.spam.com> writes:

> When I said "copy" above (C++), I meant create a new object as a copy.
> This involves copy constructor. The point is that this new object is
> *separate* from the destination object (from what's on the left side
> of assignment operator), so even if there are errors, they do not
> influence any of the two objects which were originally involved. After
> this new helper object is constructed (which means: *successfully*
> constructed), it's "injected" in the destination object by means of
> swapping the bowels.

If you want this semantics in Ada, you can program it yourself; it's
just not what is defined for Controlled types.


-- 
-- Stephe



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-11-30 22:06   ` Björn Persson
  2005-11-30 23:52     ` Randy Brukardt
@ 2005-12-01  5:26     ` Jeffrey R. Carter
  2005-12-02 23:51       ` Robert A Duff
  1 sibling, 1 reply; 37+ messages in thread
From: Jeffrey R. Carter @ 2005-12-01  5:26 UTC (permalink / raw)


Bj�rn Persson wrote:

> Can't Maciej's concerns be applied to step 6? What to do about 
> exceptions that happen while the new X is being adjusted, after the old 
> X has been finalized?

You can get Storage_Error if Adjust allocates storage. Other sorts of exceptions 
should have occurred during the adjustment of the intermediate object, and so 
corrected before the assignment to X.

Storage_Error is a strange beast; there's no guarantee that you can do anything 
about it. There may not even be enough storage available to execute an exception 
handler.

-- 
Jeff Carter
"Crucifixion's a doddle."
Monty Python's Life of Brian
82



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-11-30 16:19   ` Maciej Sobczak
  2005-12-01  0:05     ` Stephen Leake
@ 2005-12-01  9:21     ` Dmitry A. Kazakov
  2005-12-01 10:46       ` Maciej Sobczak
  1 sibling, 1 reply; 37+ messages in thread
From: Dmitry A. Kazakov @ 2005-12-01  9:21 UTC (permalink / raw)


On Wed, 30 Nov 2005 17:19:23 +0100, Maciej Sobczak wrote:

> Dmitry A. Kazakov wrote:
> 
>>>Now, the interesting part: let's say that during adjustment (3.) some 
>>>error happened (like low memory condition or whatever) that resulted in 
>>>raising an exception
>> 
>> ARM 7.6.1 reads: "It is a bounded error for a call on Finalize or Adjust to
>> propagate an exception.
>> [...]
>> For an Adjust invoked as part of an assignment operation, any other
>> adjustments due to be performed are performed, and then Program_Error is
>> raised."
> 
> OK, that brings some light, but does not solve my problem. :)

Which is not solvable. The real problem is that you cannot both look into
an atomic abstraction and pretend that this look is consistent with the
abstraction. The problem comes with user-defined constructors. The language
generated ones are composed out of parts which can be reversible, provided
the language designer knows how to do it. But a user-defined constructor is
irreversible, otherwise than by, again, a user-defined destructor. Now you
are sitting in a rocket, a user-defined constructor has just turned on the
ignition, and oops, you notice that you have left your hat at home...

>> Here the semantics of "copy", "inject", "duplicate" is ill-defined. In
>> general, you can copy a set of bits, but you cannot an object without
>> defining it in the terms copy-constructor.
> 
> When I said "copy" above (C++), I meant create a new object as a copy. 
> This involves copy constructor. The point is that this new object is 
> *separate* from the destination object (from what's on the left side of 
> assignment operator),

1. This changes little. Consider an exception raised while construction of
the copy. The copy is corrupt. Both to destruct or to just deallocate it
could be wrong.

2. User-defined constructor as a concept is useless if I cannot construct
in-place. Consider construction of non-movable objects containing self
references or bound to definite memory locations.

>> In Ada's case copy-constructor
>> is defined as Bitwise copy + Adjust.
> 
> The assignment is defined as Finalize + Bitwise copy + Adjust.
> And it's the fact that Finalize comes first that bothers me.

Me too, but exception-safety is irrelevant to the issue. If Ada should ever
have user-defined constructors and assignment (because Ada.Finalization is
not), then I would really like to have an access to the left part of the
assignment. IMO, the model could be:

1. Compiler-generated assignment is generated as Finalize +
Copy-constructor.

2. User-defined assignment can override it. However, there are many tough
problems. The assignment should be able to change the constraints (i.e.
bounds, discriminants, tags.) It should be composable against aggregation.
It should have access to the left part, but also be able to override it
in-place.

AFAIK, there is no language which does it right.

>> Which is
>> equivalently means that in general case you cannot define any reasonable
>> semantics for its partial completion.
> 
> Note that in the example above there is no "partial completion". On the 
> contrary - either the operation completes successfully or it fails 
> *wihout* modifying anything. Moreover, the scheme does not force me to 
> ignore the error nor anything like this, I can let it go to the place 
> where there's enough context to really handle it.

No, as I tried to explain above, it just moves the problem to construction
of a temporal object.

>>>What do you do to avoid surprises?
>> 
>> Don't let exceptions propagate.
> 
> What do you mean by "don't propagate"? What if there is an exception 
> that was raised by the run-time (like low memory condition) in the 
> middle of adjusting the whole stack? What should I do with the part that 
> was aleady adjusted (duplicated)? What should I do with the part that 
> was not yet adjusted? Should I clean up what's already done and leave 
> the destination stack as empty and shut the exception up, thus 
> preventing the higer-level code from properly handling it?
> 
> Is it possible to have assignment with strong exception guarantee?

For a user-defined assignment the answer is no, or it is a halting problem.
For generated assignments it depends on whether its components are
reversible.

-- 
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-12-01  9:21     ` Dmitry A. Kazakov
@ 2005-12-01 10:46       ` Maciej Sobczak
  2005-12-01 15:08         ` Dmitry A. Kazakov
  0 siblings, 1 reply; 37+ messages in thread
From: Maciej Sobczak @ 2005-12-01 10:46 UTC (permalink / raw)


Dmitry A. Kazakov wrote:

>>>ARM 7.6.1 reads: "It is a bounded error for a call on Finalize or Adjust to
>>>propagate an exception.

>>OK, that brings some light, but does not solve my problem. :)

> Which is not solvable. [...]
> Now you
> are sitting in a rocket, a user-defined constructor has just turned on the
> ignition, and oops, you notice that you have left your hat at home...

Now I'm copying a Stack object, a user-defined Finalize dutifully 
cleaned up the "old" state, the Adjust started to duplicate the 
structure and it notices that there is not enough memory to do this...

The analogy with a rocket and a hat does not fit, unless you want me to 
believe that Ada is good enough for rockets, but not good enough for 
stacks. ;)

>>When I said "copy" above (C++), I meant create a new object as a copy. 
>>This involves copy constructor. The point is that this new object is 
>>*separate* from the destination object (from what's on the left side of 
>>assignment operator),
> 
> 1. This changes little. Consider an exception raised while construction of
> the copy. The copy is corrupt. Both to destruct or to just deallocate it
> could be wrong.

This changes a lot. I *know* how to deal with exceptions in 
constructors, especially if their only effect is to build structures in 
memory (and that applies to Stack in particular). Note that when the 
constructor throws/raises, the object is considered to be never created 
and its destructor is therefore never called.

> 2. User-defined constructor as a concept is useless if I cannot construct
> in-place. Consider construction of non-movable objects containing self
> references or bound to definite memory locations.

You can do this in constructors. But *copy* constructors are for copying 
objects and not all objects need to be copyable in the first place. I 
don't expect objects bound to definite memory locations to be copyable.
Stacks can be copyable (all standard containers are, for that matter).


>>The assignment is defined as Finalize + Bitwise copy + Adjust.
>>And it's the fact that Finalize comes first that bothers me.
> 
> Me too, but exception-safety is irrelevant to the issue. If Ada should ever
> have user-defined constructors and assignment (because Ada.Finalization is
> not), then I would really like to have an access to the left part of the
> assignment. IMO, the model could be:
> 
> 1. Compiler-generated assignment is generated as Finalize +
> Copy-constructor.

No. Copy-constructor might fail and you've already sold your house.
For exception safety, it should be the other way round.

> 2. User-defined assignment can override it. However, there are many tough
> problems. The assignment should be able to change the constraints (i.e.
> bounds, discriminants, tags.) It should be composable against aggregation.
> It should have access to the left part, but also be able to override it
> in-place.
> 
> AFAIK, there is no language which does it right.

What's wrong with the example I provided in my previous post?


>>Note that in the example above there is no "partial completion". On the 
>>contrary - either the operation completes successfully or it fails 
>>*wihout* modifying anything. Moreover, the scheme does not force me to 
>>ignore the error nor anything like this, I can let it go to the place 
>>where there's enough context to really handle it.
> 
> No, as I tried to explain above, it just moves the problem to construction
> of a temporal object.

Which is exactly what is needed. If it fails, it can be rolled-back 
without touching the original left object.
Note that we are talking about copyable objects and not all objects need 
to be copyable in the first place. Stacks and other containers deal with 
memory structures and they *are* copyable, and the failures in their 
copy constructors are easy to deal with in the sense that rolling back 
the constructor does not influence neither the assignee nor the exception.

>>Is it possible to have assignment with strong exception guarantee?
> 
> For a user-defined assignment the answer is no, or it is a halting problem.
> For generated assignments it depends on whether its components are
> reversible.

The problem is that whenever I think that a type should be Controlled, 
it is because of these two things (always):
1. The type should be copyable (again, not all types should).
2. The default copy behavior is bad, because it leads to state sharing 
or other anomalies.

The difficult part is that in *all* cases that I've seen or written (in 
C++), it was not possible to guarantee that the duplication of state can 
be performed without errors. This certainly applies to all types that 
have dynamic memory structures behind them, starting from innocent 
unbounded string. Ada has unbounded string, recommended on this group in 
various contexts. How does it dolve this problem? Does it?


-- 
Maciej Sobczak : http://www.msobczak.com/
Programming    : http://www.msobczak.com/prog/



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-12-01 10:46       ` Maciej Sobczak
@ 2005-12-01 15:08         ` Dmitry A. Kazakov
  2005-12-02  4:17           ` Randy Brukardt
  0 siblings, 1 reply; 37+ messages in thread
From: Dmitry A. Kazakov @ 2005-12-01 15:08 UTC (permalink / raw)


On Thu, 01 Dec 2005 11:46:42 +0100, Maciej Sobczak wrote:

> Dmitry A. Kazakov wrote:
> 
>> 1. This changes little. Consider an exception raised while construction of
>> the copy. The copy is corrupt. Both to destruct or to just deallocate it
>> could be wrong.
> 
> This changes a lot. I *know* how to deal with exceptions in 
> constructors, especially if their only effect is to build structures in 
> memory (and that applies to Stack in particular). Note that when the 
> constructor throws/raises, the object is considered to be never created 
> and its destructor is therefore never called.

I.e. whatever side effects the failed constructor has caused, they will
persist. The problem is not in finalization of the target. The problem is
in the system state = whether objects invariants hold between their
construction and finalization.

>> 2. User-defined constructor as a concept is useless if I cannot construct
>> in-place. Consider construction of non-movable objects containing self
>> references or bound to definite memory locations.
> 
> You can do this in constructors. But *copy* constructors are for copying 
> objects

Not quite. They are for constructing objects. Copying is allocation +
construction.

> and not all objects need to be copyable in the first place. I 
> don't expect objects bound to definite memory locations to be copyable.
> Stacks can be copyable (all standard containers are, for that matter).

Graph of linked nodes is a counter example, it cannot be moved, but can be
copied.

[...]
> What's wrong with the example I provided in my previous post?
[...]
> Which is exactly what is needed. If it fails, it can be rolled-back 
> without touching the original left object.

As I said, in my view it has little to do with exception safety. What you
actually wish is to have access to the left side of assignment. Controlled
types do not provide this functionality. End of story.

As for the pattern:

1. clone implementation;
2. copy reference;
3. collect unused object

which you posed as an example. It cannot be disguised as ":=". You have to
name it otherwise, like "Replace". [And neither will be double dispatching
too, which is much worse thing, if you are looking something really bad.
(:-))]

I don't count the pattern for a big deal, because in most cases it is a
*bad* pattern. It is bad because it overloads ":=" with semantics the
latter should not have (i.e. non-trivial memory allocation.) and it has a
heavy performance penalty. I'm using reference copy postponing cloning for
later, on demand. In my component library Stack is a limited type. Set is
indeed copyable. It uses the reference semantics, so ":=" just increments
the reference count.

> The difficult part is that in *all* cases that I've seen or written (in 
> C++), it was not possible to guarantee that the duplication of state can 
> be performed without errors. This certainly applies to all types that 
> have dynamic memory structures behind them, starting from innocent 
> unbounded string. Ada has unbounded string, recommended on this group in 
> various contexts. How does it dolve this problem? Does it?

I think Randy has answered that.

-- 
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-12-01 15:08         ` Dmitry A. Kazakov
@ 2005-12-02  4:17           ` Randy Brukardt
  2005-12-02  9:29             ` Maciej Sobczak
  2005-12-02 23:21             ` Robert A Duff
  0 siblings, 2 replies; 37+ messages in thread
From: Randy Brukardt @ 2005-12-02  4:17 UTC (permalink / raw)


"Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message
news:c5f58jf7dlvo.jwmx6q1b5l24$.dlg@40tude.net...
> On Thu, 01 Dec 2005 11:46:42 +0100, Maciej Sobczak wrote:
...
> > This changes a lot. I *know* how to deal with exceptions in
> > constructors, especially if their only effect is to build structures in
> > memory (and that applies to Stack in particular). Note that when the
> > constructor throws/raises, the object is considered to be never created
> > and its destructor is therefore never called.
>
> I.e. whatever side effects the failed constructor has caused, they will
> persist. The problem is not in finalization of the target. The problem is
> in the system state = whether objects invariants hold between their
> construction and finalization.

Right. Such a constructor ought to clean up its mess before propagating an
exception. If it doesn't, you'll have memory leaks and other such badness.

> [...]
> > What's wrong with the example I provided in my previous post?
> [...]
> > Which is exactly what is needed. If it fails, it can be rolled-back
> > without touching the original left object.
>
> As I said, in my view it has little to do with exception safety. What you
> actually wish is to have access to the left side of assignment. Controlled
> types do not provide this functionality. End of story.

Right again. Ada doesn't really have user-defined assignment; if you
*really* need that you have to use a procedure.

And in any case, what you are asking for would be contrary to the efficiency
goals of Ada. You're saying that all assignment *have to be* made to
temporaries. I can imagine some language working that way, but it's
performance would be several times slower than C++. We have enough trouble
with people thinking Ada is slow as it is, without duplicating all of the
effort twice. There is a lot of words in the Ada standard specifically to
allow implementers to optimize (eliminating the assignment temporary, for
instance).

Ada's model is that a failed assignment leaves the target corrupt. You can
mitigate that, but not completely eliminate it. If that is unacceptable for
your application (and I can think of few for which that would be the case),
then you have to avoid ":=" (most likely by using a limited type). At least
Ada 200Y improves the support for limited types a lot, so that you no longer
have to give up fancy constructors, aggregates, and constants when you use
them.

In your example of a failed stack assignment, the Adjust routine ought to
clean up the mess if Storage_Error is raised, and leave the target Stack
empty. Is this ideal? Possibily not, but it hardly matters, because there is
no clean way to know what might or might not have been done when the
assignment fails (read 11.6 if you believe otherwise); recovery means
exiting out and rolling back far more than one object. (And there really is
no safe, portable recovery from out-of-memory conditions -- you can only
figure out what works with a specific compiler and target and do that.)

...
> > The difficult part is that in *all* cases that I've seen or written (in
> > C++), it was not possible to guarantee that the duplication of state can
> > be performed without errors. This certainly applies to all types that
> > have dynamic memory structures behind them, starting from innocent
> > unbounded string. Ada has unbounded string, recommended on this group in
> > various contexts. How does it dolve this problem? Does it?
>
> I think Randy has answered that.

Probably not specifically. Obviously it depends on the implementation. I
just looked at ours, and it does nothing at all to handle memory issues in
Adjust. That means that the object would fail the invariants after such an
assignment. (I didn't actually realize that; it would be better to null the
pointer in that case...) And presumably, it would eventually access through
a deallocated pointer. But that's all a correct (if unfriendly)
implementation of Ada, because the object is abnormal, and any access to it
is erroneous -- see 13.9.1. (Humm, this actually isn't as clear as it ought
to be; one could argue that Storage_Error isn't a "language-defined check"
(its not called that in 11.1(6)). But surely it is intended to be covered;
it's hard to imagine a case that is more likely to corrupt things than
running out of memory. And it is indexed as a check. So I apply Dewar's rule
[the Standard never says anything silly]. Anyway, sorry about the language
lawyer musings...)

Moral: don't touch the left-hand side of any assignment after it failed
raising an exception, other than to assign a new value to the *entire*
value. If you want some other semantics, don't fool yourself and others by
calling it ":="; use limited types and appropriate copying procedures.

                              Randy.






^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-12-02  4:17           ` Randy Brukardt
@ 2005-12-02  9:29             ` Maciej Sobczak
  2005-12-02 18:12               ` tmoran
  2005-12-02 23:21             ` Robert A Duff
  1 sibling, 1 reply; 37+ messages in thread
From: Maciej Sobczak @ 2005-12-02  9:29 UTC (permalink / raw)


Randy Brukardt wrote:

> Right. Such a constructor ought to clean up its mess before propagating an
> exception. If it doesn't, you'll have memory leaks and other such badness.

Sure. This is clear and can be applied to all subroutines, not only 
constructors.


> Ada doesn't really have user-defined assignment; if you
> *really* need that you have to use a procedure.

OK, and now it's bright clear to me. I got an impression that Controlled 
types can buy me the same syntax sugar with the same flexibility in 
exception-safety guarantees that I have with assignment operators in 
C++. It's not bad that they don't - but I have to know it.

> And in any case, what you are asking for would be contrary to the efficiency
> goals of Ada. You're saying that all assignment *have to be* made to
> temporaries.

No. In my first post in this thread I have presented 4 levels of 
exception safety (some practitioners don't count level 0).
It's *me* (the designer of the type) who decides which level and which 
guarantee is appropriate for which operation, and what's more important, 
when it's *worth* its tradeoffs.

Interestingly, in the Stack example there is no performance tradeoff - 
you *have* to do both cleanup and state duplication anyway, no matter 
what's the provided guarantee, but by introducing the temporary object I 
can force the specific *order* of those operations (first duplicate, 
then clean up) that gives me the strong guarantee - which means 
commit-or-rollback. It's a free lunch in C++ and therefore there's no 
reason not to have it in types like string, stack, etc. In particular, 
there's no efficiency loss. OK, you can argue that in this scheme you 
have to first create a duplicate and then destroy the old state, which 
means that for some short period of time we consume more memory (which, 
funny, makes it more likely to fail because of memory shortage :) ) and 
that can result in lower cache hit rates and this kind of stuff. But as 
already said - it's *my* responsibility to judge the tradeoffs for each 
case separately. It's not true that this should be done everywhere.

> Ada's model is that a failed assignment leaves the target corrupt.

Which is equivalent to level 0 in the classification from the beginning 
of this thread. That's OK, as far as everybody knows it.

> In your example of a failed stack assignment, the Adjust routine ought to
> clean up the mess if Storage_Error is raised, and leave the target Stack
> empty.

Which would give level 1 (coherent state, no resources leaked).

> Moral: don't touch the left-hand side of any assignment after it failed
> raising an exception, other than to assign a new value to the *entire*
> value. If you want some other semantics, don't fool yourself and others by
> calling it ":="; use limited types and appropriate copying procedures.

Which is now clear.

The whole subject came from the fact that I want to learn Ada and that 
at some point I decided to write an "obligatory" exercise, which is a 
container or something like this. This led me to posing my questions. As 
usual, I've learnt something about Ada and that's actually what really 
matters. Thank you for all the replies.


-- 
Maciej Sobczak : http://www.msobczak.com/
Programming    : http://www.msobczak.com/prog/



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-12-02  9:29             ` Maciej Sobczak
@ 2005-12-02 18:12               ` tmoran
  2005-12-02 19:15                 ` Robert A Duff
  0 siblings, 1 reply; 37+ messages in thread
From: tmoran @ 2005-12-02 18:12 UTC (permalink / raw)


> it's *my* responsibility to judge the tradeoffs for each case separately.
  If you want "X := Y;" to leave X unchanged if there are problems during
the assignment, consider how you might handle a similar problem if you were
copying an important file on disk.  You would probably do something like
  rename X -> X.bak
  copy Y -> X
  delete X.bak
so that if their was a failure during copy you could still retrieve the
file's data.  How about doing something similar for the stack object?
  type stack is ...
    backup_copy : access stack;
Finalize then makes a backup copy (perhaps just by swapping pointers
around), which Adjust then deletes when it successfuly finishes, or which
Adjust's exception handler uses to restore X if there's a problem.
Finalize also puts the backup_copy, with a "delete after time T" time
marker, on a queue for later garbage collection if this is a "terminal"
Finalize - analogous to a nightly "delete *.bak".



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-12-02 18:12               ` tmoran
@ 2005-12-02 19:15                 ` Robert A Duff
  2005-12-02 21:42                   ` tmoran
  0 siblings, 1 reply; 37+ messages in thread
From: Robert A Duff @ 2005-12-02 19:15 UTC (permalink / raw)


tmoran@acm.org writes:

> > it's *my* responsibility to judge the tradeoffs for each case separately.
>   If you want "X := Y;" to leave X unchanged if there are problems during
> the assignment, consider how you might handle a similar problem if you were
> copying an important file on disk.  You would probably do something like
>   rename X -> X.bak
>   copy Y -> X
>   delete X.bak
> so that if their was a failure during copy you could still retrieve the
> file's data.  How about doing something similar for the stack object?
>   type stack is ...
>     backup_copy : access stack;
> Finalize then makes a backup copy (perhaps just by swapping pointers
> around), which Adjust then deletes when it successfuly finishes, or which
> Adjust's exception handler uses to restore X if there's a problem.
> Finalize also puts the backup_copy, with a "delete after time T" time
> marker, on a queue for later garbage collection if this is a "terminal"
> Finalize - analogous to a nightly "delete *.bak".

But X := Y overwrites X before calling Adjust on it, so you can't store
the backup copy, or any way of accessing the backup copy, in X.

- Bob



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-12-02 19:15                 ` Robert A Duff
@ 2005-12-02 21:42                   ` tmoran
  2005-12-06  9:00                     ` Maciej Sobczak
  0 siblings, 1 reply; 37+ messages in thread
From: tmoran @ 2005-12-02 21:42 UTC (permalink / raw)


>But X := Y overwrites X before calling Adjust on it, so you can't store
>the backup copy, or any way of accessing the backup copy, in X.
  Right.  But Adjust, in case of problems, could still find the copy of
the old X in the "to be deleted" backup queue and restore X from there.
  I didn't say this was nice, just that it was possible. ;)



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-12-02  4:17           ` Randy Brukardt
  2005-12-02  9:29             ` Maciej Sobczak
@ 2005-12-02 23:21             ` Robert A Duff
  1 sibling, 0 replies; 37+ messages in thread
From: Robert A Duff @ 2005-12-02 23:21 UTC (permalink / raw)


"Randy Brukardt" <randy@rrsoftware.com> writes:

> Probably not specifically. Obviously it depends on the implementation. I
> just looked at ours, and it does nothing at all to handle memory issues in
> Adjust. That means that the object would fail the invariants after such an
> assignment. (I didn't actually realize that; it would be better to null the
> pointer in that case...) And presumably, it would eventually access through
> a deallocated pointer. But that's all a correct (if unfriendly)
> implementation of Ada, because the object is abnormal, and any access to it
> is erroneous -- see 13.9.1. (Humm, this actually isn't as clear as it ought
> to be; one could argue that Storage_Error isn't a "language-defined check"
> (its not called that in 11.1(6)).

I think 11.5(2) can reasonably be interpreted to mean that Storage_Error
qualifies.  Certainly, that is the intent.

11.5(22) also applies, although it incorrectly talks about specific
reasons why S_E might be raised, whereas 11.1(6) says S_E can be raised
by anything at all (not just "new" and procedures and tasks).

>... But surely it is intended to be covered;

Yes, surely.

> it's hard to imagine a case that is more likely to corrupt things than
> running out of memory.

Well, I can imagine "abort", and that's just as bad (except that you can
defer aborts, but you can't defer out-of-memory).

- Bob



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-12-01  5:26     ` Jeffrey R. Carter
@ 2005-12-02 23:51       ` Robert A Duff
  0 siblings, 0 replies; 37+ messages in thread
From: Robert A Duff @ 2005-12-02 23:51 UTC (permalink / raw)


"Jeffrey R. Carter" <spam@spam.com> writes:

> Bj�rn Persson wrote:
> 
> > Can't Maciej's concerns be applied to step 6? What to do about
> > exceptions that happen while the new X is being adjusted, after the
> > old X has been finalized?
> 
> You can get Storage_Error if Adjust allocates storage. Other sorts of
> exceptions should have occurred during the adjustment of the
> intermediate object, and so corrected before the assignment to X.
> 
> Storage_Error is a strange beast; there's no guarantee that you can do
> anything about it. There may not even be enough storage available to
> execute an exception handler.

Right.  In practise, you can get away with handling Storage_Error, but
it's annoying that it is pretty-much impossible to write a handler for
Storage_Error in Ada that is guaranteed (portably) correct by the RM.
Ada is better than some languages, where a stack overflow can go
entirely undetected, and the program just starts overwriting
who-knows-what data.

But I think I've got a better way, which I would use in my own language
design.

My idea is that stack overflow is like an abort or a hardware interrupt.
An abort is asynchronous with the running code -- it can happen
anywhere, even in the middle of "X := Y".  If X:=Y can is aborted in the
middle, we say that X becomes abnormal -- perhaps its discriminants are
nonsensical, so later code can't even determine the size of X.  The
solution is to have abort-deferred regions -- regions of code where
abort can't happen.  Inside such a region, you can say X:=Y, and be sure
that X is unchanged, or Y is fully copied into it.  If somebody attempts
to abort in the middle of X:=Y, the abort will take effect at the end,
and all is well.

Same thing for hardware interrupts -- the solution is to allow
(hopefully short) regions of code where interrupts can't interrupt.

Stack overflow is asynchronous in the sense that it can happen pretty
much anywhere.  So in my fictitious language, you can have regions of
code where stack overflow can't happen.  The compiler is required to
calculate (at link time!) a static quantity that is the max stack usage
for each procedure, task, and other relevant construct.  This quantity
is an integer ranging from 0 up to the max size of the address space
(System.Memory_Size, in Ada).  Calculations use saturating arithmetic.

When you enter a no-stack-overflow region, we allocate the max size for
that region, and raise Storage_Error if that's not possible.  So it's
like an abort-deferred or interrupt-deferred region, except that the
deferral goes backward in time -- if Storage_Error _might_ be raised in
that region, we instead raise it before entering the region.

Of course, the code in a no-stack-overflow region can't do stuff that
allocates unknown amounts of stack space.  If a procedure has a local
variable of subtype String, with no compile-time-known bounds, the max
size is perhaps 2**31 bytes or so.  If a procedure is recursive, the max
size is System.Memory_Size.  If a procedure makes an indirect call (so
it _might_ be recursive), the max size is System.Memory_Size.  So you
write no-stack-overflow regions with small numbers of known-size
locals.  But that's OK -- all you want to do is log the error, clean up
some things, and return to a more-global point in the program.

If the max size for such a region is System.Memory_Size, or close to it,
the compiler should at least issue a warning, because at run time, every
execution of that thing will raise S_E.

Storage_Error also applies to "new", but that seems like an easier
problem.  The allocator can be "blamed", so heap overflow is not
asynchronous like stack overflow.  At least, for _explicit_ use of the
heap.  If the compiler is allocating activation records on the heap
or some such, then that's still an issue.

- Bob



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-12-02 21:42                   ` tmoran
@ 2005-12-06  9:00                     ` Maciej Sobczak
  2005-12-06  9:50                       ` Dmitry A. Kazakov
  0 siblings, 1 reply; 37+ messages in thread
From: Maciej Sobczak @ 2005-12-06  9:00 UTC (permalink / raw)


tmoran@acm.org wrote:
>>But X := Y overwrites X before calling Adjust on it, so you can't store
>>the backup copy, or any way of accessing the backup copy, in X.
> 
>   Right.  But Adjust, in case of problems, could still find the copy of
> the old X in the "to be deleted" backup queue and restore X from there.
>   I didn't say this was nice, just that it was possible. ;)

Except that it doesn't solve anything. The whole issue with this 
commit-or-rollback implementation is that it should not just suppress 
the exception and pretend that nothing happened - it should guarantee 
the old state and at the same time let the exception fly out to the 
place where it could be actually handled, whatever that means in the 
given context. I've started with the assumption that function ":=" is 
allowed to fail - in the sense that it can raise exceptions. It's not, 
and therefore there is no point in implementing any failover features in 
it. It has to either guarantee the success or not be provided at all and 
the type should be limited.

This brings me to the next problem. Let's say that I provide a separate 
procedure Duplicate or Copy or Assign or whatever with the 
commit-or-rollback guarantees for some type (like Stack). Now, some of 
the types in my program will have ":=" for assignment, and some others 
will have the Copy procedure, but not ":=".
I want to create a generic container or some other component that will 
copy things around internally. It has to use ":=" for some types (like 
Integer) and Copy for others (like Stack).
In C++ I solve this problem (aside the fact that there is no problem in 
the first place) with template type traits or some other application of 
template specializations.
What about Ada?

-- 
Maciej Sobczak : http://www.msobczak.com/
Programming    : http://www.msobczak.com/prog/



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-12-06  9:00                     ` Maciej Sobczak
@ 2005-12-06  9:50                       ` Dmitry A. Kazakov
  2005-12-06 18:34                         ` Jeffrey R. Carter
  0 siblings, 1 reply; 37+ messages in thread
From: Dmitry A. Kazakov @ 2005-12-06  9:50 UTC (permalink / raw)


On Tue, 06 Dec 2005 10:00:39 +0100, Maciej Sobczak wrote:

> This brings me to the next problem. Let's say that I provide a separate 
> procedure Duplicate or Copy or Assign or whatever with the 
> commit-or-rollback guarantees for some type (like Stack). Now, some of 
> the types in my program will have ":=" for assignment, and some others 
> will have the Copy procedure, but not ":=".
> I want to create a generic container or some other component that will 
> copy things around internally. It has to use ":=" for some types (like 
> Integer) and Copy for others (like Stack).
> In C++ I solve this problem (aside the fact that there is no problem in 
> the first place) with template type traits or some other application of 
> template specializations.
> What about Ada?

generic
   type Object is limited private;
   with procedure Deep_Copy (Left : in out Object; Right : Object) is <>;
package Container is
   ...
end Container;
-------------------------------
with Container;
generic
   type Object is private;
package Specialized_Container is
   procedure Deep_Copy (Left : in out Object; Right : Object);
   pragma Inline (Deep_Copy);
   package Copying_By_Assignment is new Container (Object);   
end Specialized_Container;
-------------------------------
package body Specialized_Container is
   procedure Deep_Copy (Left : in out Object; Right Object) is
   begin
      Left := Right;
   end Deep_Copy;
end Specialized_Container;

Note also that your example is not much realistic. Transaction model is
expensive. One usually does not compose transactions. This means that
components of a container will be copied destructively, *after* necessary
memory allocation. Only the upper level will take care of a possibility to
roll things back. Thus it makes much sense to distinguish light-weight ":="
(which can't fail) and heavy-weight "Copy".

The container itself could be a red-black tree, which supports roll-backs
after mutations.

-- 
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-11-30 21:02 ` Jeffrey R. Carter
  2005-11-30 22:06   ` Björn Persson
@ 2005-12-06 11:41   ` Peter C. Chapin
  2005-12-06 12:50     ` Jean-Pierre Rosen
  2005-12-06 13:06     ` Dmitry A. Kazakov
  1 sibling, 2 replies; 37+ messages in thread
From: Peter C. Chapin @ 2005-12-06 11:41 UTC (permalink / raw)


Jeffrey R. Carter wrote:

>  For an assignment_statement, after the name and expression have been 
> evaluated, and any conversion (including constraint checking) has been 
> done, an anonymous object is created, and the value is assigned into it; 
> that is, the assignment operation is applied. (Assignment includes value 
> adjustment.) The target of the assignment_statement is then finalized. 
> The value of the anonymous object is then assigned into the target of 
> the assignment_statement. Finally, the anonymous object is finalized. As 
> explained below, the implementation may eliminate the intermediate 
> anonymous object, so this description subsumes the one given in 5.2, 
> ``Assignment Statements''.

I don't understand the point of the intermediate object. Since an adjust 
is done when assigning to the target object, what advantage does 
introducing the intermediate object have over just assigning to the 
target from the original source? The description above makes it sound 
like the system makes a pointless copy of the source object.

A (perhaps) related question: If exceptions can't propagate from Adjust, 
how does the caller know that X in 'X := Y' is in an invalid state 
should the Adjust fail? Is it necessary to set a flag in X and thus do

X := Y;
if X.invalid then
   raise Failed_Assignment;
end if;

Peter



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-12-06 11:41   ` Peter C. Chapin
@ 2005-12-06 12:50     ` Jean-Pierre Rosen
  2005-12-06 13:06     ` Dmitry A. Kazakov
  1 sibling, 0 replies; 37+ messages in thread
From: Jean-Pierre Rosen @ 2005-12-06 12:50 UTC (permalink / raw)


Peter C. Chapin a �crit :
> Jeffrey R. Carter wrote:
> I don't understand the point of the intermediate object. Since an adjust 
> is done when assigning to the target object, what advantage does 
> introducing the intermediate object have over just assigning to the 
> target from the original source? The description above makes it sound 
> like the system makes a pointless copy of the source object.
> 
Think about reference counting for example. Without the intermediate 
object, the counter might drop to 0 before assignment, therefore 
(incorrectly) freeing the object

-- 
---------------------------------------------------------
            J-P. Rosen (rosen@adalog.fr)
Visit Adalog's web site at http://www.adalog.fr



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-12-06 11:41   ` Peter C. Chapin
  2005-12-06 12:50     ` Jean-Pierre Rosen
@ 2005-12-06 13:06     ` Dmitry A. Kazakov
  1 sibling, 0 replies; 37+ messages in thread
From: Dmitry A. Kazakov @ 2005-12-06 13:06 UTC (permalink / raw)


On Tue, 06 Dec 2005 06:41:28 -0500, Peter C. Chapin wrote:

> X := Y;
> if X.invalid then
>    raise Failed_Assignment;
> end if;

X := Y;
if X /= Y then ...

and of course

if 2+2 /= 5 then
  4 := 5;
end if;

(:-))

-- 
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-12-06  9:50                       ` Dmitry A. Kazakov
@ 2005-12-06 18:34                         ` Jeffrey R. Carter
  2005-12-06 19:34                           ` Randy Brukardt
  2005-12-06 20:43                           ` Dmitry A. Kazakov
  0 siblings, 2 replies; 37+ messages in thread
From: Jeffrey R. Carter @ 2005-12-06 18:34 UTC (permalink / raw)


Dmitry A. Kazakov wrote:

> generic
>    type Object is limited private;
>    with procedure Deep_Copy (Left : in out Object; Right : Object) is <>;
> package Container is
>    ...
> end Container;
> -------------------------------
> with Container;
> generic
>    type Object is private;
> package Specialized_Container is
>    procedure Deep_Copy (Left : in out Object; Right : Object);
>    pragma Inline (Deep_Copy);
>    package Copying_By_Assignment is new Container (Object);   
> end Specialized_Container;
> -------------------------------
> package body Specialized_Container is
>    procedure Deep_Copy (Left : in out Object; Right Object) is
>    begin
>       Left := Right;
>    end Deep_Copy;
> end Specialized_Container;

There's a gotcha in here. Suppose we have

subtype S is Integer range 3 .. 4;

and we instantiate

package S_Container is new Specialized_Container (Object => S);

Now suppose that Container has something like

procedure Op (Item : in Object) is
    X : Object;
begin -- Op
    Deep_Copy (Left => X, Right => Item);
    ...
end Op;

This is actually quite likely for a container, except X will be a component of 
the structure.

For scalars, there is a check on "in" and "in out" parameters that the actual 
value is of the subtype; Constraint_Error is raised if it is not. The check is 
likely to fail in this case; X probably is not in 3 .. 4.

So, for Container to work correctly for all possible actual types, the 
assignment procedure must have Left be mode "out". Now the uninitialized actual 
for Left is not checked on entry to the procedure, and it works correctly for 
scalars. For composite types, there is a whole collection of situations in which 
"out" really means "in out", so the user can still write a meaningful procedure 
that can inspect the contents of Left.

Personally, I would have preferred

procedure R'Assign (To : in out R; From : in R);

for any record type R. This can be redefined by the user:

for R'Assign use My_Assignment_Procedure;

I have seen objections to this approach, but none that aren't handled by one of 
the following rules:

* Within the body of a procedure used to implement 'Assign, ":=" refers to the 
predefined, bitwise copy assignment.

or

* There exists a procedure

R'Bitwise_Copy (To : in out R; From : in R);

that cannot be redefined by the user and is the default procedure for R'Assign. 
'Bitwise_Copy can be called explicitly inside a procedure used to implement 
'Assign to invoke default assignment.

Perhaps I'm missing something, but in any case, it's an elephant.

-- 
Jeff Carter
"English bed-wetting types."
Monty Python & the Holy Grail
15



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-12-06 18:34                         ` Jeffrey R. Carter
@ 2005-12-06 19:34                           ` Randy Brukardt
  2005-12-06 21:20                             ` Dmitry A. Kazakov
  2005-12-07  1:57                             ` Jeffrey R. Carter
  2005-12-06 20:43                           ` Dmitry A. Kazakov
  1 sibling, 2 replies; 37+ messages in thread
From: Randy Brukardt @ 2005-12-06 19:34 UTC (permalink / raw)


"Jeffrey R. Carter" <spam@spam.com> wrote in message
news:2Rklf.171$n1.114@newsread2.news.pas.earthlink.net...
> Personally, I would have preferred
>
> procedure R'Assign (To : in out R; From : in R);
>
> for any record type R. This can be redefined by the user:
>
> for R'Assign use My_Assignment_Procedure;
>
> I have seen objections to this approach, but none that aren't handled by
one of
> the following rules:

This was the original idea for Ada 95, but it doesn't work. That's because
the object on the left-hand side may come into existence because of the
outer assignment, or disappear because of the assignment. The beauty (and
curse) of Adjust is that it can be called by itself when needed, or with an
appropriate Finalize.

You can't, in general, read the object that you're assigning into. That
means that user-defined assignment in Ada can never be as powerful as that
in other languages (unless you somehow prevent the types from being used in
discriminant-dependent components - which would probably be a generic
contract problem).

For an another explanation of this, see ARM 7.6(17.a-17.h).
http://www.adaic.com/standards/95aarm/html/AA-A-5-3.html

The other issues are solveable, but this one is not.

                     Randy Brukardt





^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-12-06 18:34                         ` Jeffrey R. Carter
  2005-12-06 19:34                           ` Randy Brukardt
@ 2005-12-06 20:43                           ` Dmitry A. Kazakov
  2005-12-07  2:00                             ` Jeffrey R. Carter
  1 sibling, 1 reply; 37+ messages in thread
From: Dmitry A. Kazakov @ 2005-12-06 20:43 UTC (permalink / raw)


On Tue, 06 Dec 2005 18:34:38 GMT, Jeffrey R. Carter wrote:

> Dmitry A. Kazakov wrote:
> 
>> generic
>>    type Object is limited private;
>>    with procedure Deep_Copy (Left : in out Object; Right : Object) is <>;
>> package Container is
>>    ...
>> end Container;
>> -------------------------------
>> with Container;
>> generic
>>    type Object is private;
>> package Specialized_Container is
>>    procedure Deep_Copy (Left : in out Object; Right : Object);
>>    pragma Inline (Deep_Copy);
>>    package Copying_By_Assignment is new Container (Object);   
>> end Specialized_Container;
>> -------------------------------
>> package body Specialized_Container is
>>    procedure Deep_Copy (Left : in out Object; Right Object) is
>>    begin
>>       Left := Right;
>>    end Deep_Copy;
>> end Specialized_Container;
> 
> There's a gotcha in here. Suppose we have
> 
> subtype S is Integer range 3 .. 4;
> 
> and we instantiate
> 
> package S_Container is new Specialized_Container (Object => S);
> 
> Now suppose that Container has something like
> 
> procedure Op (Item : in Object) is
>     X : Object;
> begin -- Op
>     Deep_Copy (Left => X, Right => Item);
>     ...
> end Op;
> 
> This is actually quite likely for a container, except X will be a component of 
> the structure.
> 
> For scalars, there is a check on "in" and "in out" parameters that the actual 
> value is of the subtype; Constraint_Error is raised if it is not. The check is 
> likely to fail in this case; X probably is not in 3 .. 4.
>
> So, for Container to work correctly for all possible actual types, the 
> assignment procedure must have Left be mode "out". Now the uninitialized actual 
> for Left is not checked on entry to the procedure, and it works correctly for 
> scalars. For composite types, there is a whole collection of situations in which 
> "out" really means "in out", so the user can still write a meaningful procedure 
> that can inspect the contents of Left.

Good point.

> Personally, I would have preferred
> 
> procedure R'Assign (To : in out R; From : in R);
> 
> for any record type R. This can be redefined by the user:
> 
> for R'Assign use My_Assignment_Procedure;
> 
> I have seen objections to this approach, but none that aren't handled by one of 
> the following rules:
> 
> * Within the body of a procedure used to implement 'Assign, ":=" refers to the 
> predefined, bitwise copy assignment.
> 
> or
> 
> * There exists a procedure
> 
> R'Bitwise_Copy (To : in out R; From : in R);

I think that a more general approach could be to have some naming
convention for the base type. When a type is composed by some predefined
operation like type ... is record I wished to have a name of the subtype
that still has all predefined operations. Maybe this name should be only
visible in the private part of the unit.

> that cannot be redefined by the user and is the default procedure for R'Assign. 
> 'Bitwise_Copy can be called explicitly inside a procedure used to implement 
> 'Assign to invoke default assignment.

But what about the following problems / issues:

1. How will it work with indefinite types?

2. Safe inheritance by derived types and composition out of assignments of
the components.

3. Dispatching on the arguments.

4. Interplay of assignments of class-wide and specific objects.

5. When initialization is construction and when it is assignment. How much
freedom should the compiler have to choose?

6. How should interact "functional" and "procedural" assignments? Let I
override it as a procedure. Then derive and override now as a function?

> Perhaps I'm missing something, but in any case, it's an elephant.

Yes.

-- 
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-12-06 19:34                           ` Randy Brukardt
@ 2005-12-06 21:20                             ` Dmitry A. Kazakov
  2005-12-07  1:57                             ` Jeffrey R. Carter
  1 sibling, 0 replies; 37+ messages in thread
From: Dmitry A. Kazakov @ 2005-12-06 21:20 UTC (permalink / raw)


On Tue, 6 Dec 2005 13:34:29 -0600, Randy Brukardt wrote:

> This was the original idea for Ada 95, but it doesn't work. That's because
> the object on the left-hand side may come into existence because of the
> outer assignment, or disappear because of the assignment. The beauty (and
> curse) of Adjust is that it can be called by itself when needed, or with an
> appropriate Finalize.
> 
> You can't, in general, read the object that you're assigning into. That
> means that user-defined assignment in Ada can never be as powerful as that
> in other languages (unless you somehow prevent the types from being used in
> discriminant-dependent components - which would probably be a generic
> contract problem).
>
> For an another explanation of this, see ARM 7.6(17.a-17.h).
> http://www.adaic.com/standards/95aarm/html/AA-A-5-3.html
> 
> The other issues are solveable, but this one is not.

The question is at which cost. We could consider assignment composed out of 
two components. For any type T:

type T'Assignment_Data is tagged limited null record;
  -- predefined and can be extended

function T'Preassign (Left : in out T; Right : T)
   return T'Assignment_Data'Class;
procedure T'Postassign
   (Left : in out T; Right : T; Data : T'Assignment_Data'Class);
 
First T'Preassign is called. It can look at the left side, back up 
everything it needs in the result. It could even make rendezvous with the 
task components of Left, which is impossible now. Then the left side is 
finalized, the discriminants and constraints are set to ones of Right, some 
components are constructed and T'Postassign is called. Its parameter Data 
carries what T'Preassign has returned. After completion of T'Postassign 
Data is destroyed.

-- 
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-12-06 19:34                           ` Randy Brukardt
  2005-12-06 21:20                             ` Dmitry A. Kazakov
@ 2005-12-07  1:57                             ` Jeffrey R. Carter
  2005-12-08  0:50                               ` Randy Brukardt
  1 sibling, 1 reply; 37+ messages in thread
From: Jeffrey R. Carter @ 2005-12-07  1:57 UTC (permalink / raw)


Randy Brukardt wrote:
> 
> For an another explanation of this, see ARM 7.6(17.a-17.h).
> http://www.adaic.com/standards/95aarm/html/AA-A-5-3.html

Is it meaningful that you reference 7.6 and provide the URL of 5.3?

> The other issues are solveable, but this one is not.

This still doesn't seem like a problem to me. Suppose R is a discriminated 
record with default:

type R (D : D_Type := D_Type'First) is record
    ...
end record;

and we want to write Assign:

procedure Assign (To : in out R; From : in R);

1st thing is to check if To is constrained; if so, From must have matching 
constraints:

if To'Constrained and then To.D /= From.D then
    raise Constraint_Error;
end if;

If To is unconstrained, and we need to (be able to) change the constraints, we 
use an intermediate object:

Not_Constrained : declare
    Result : R (D => New_D);
begin -- Not_Constrained
    -- Assign to components of Result
    To := Result; -- or R'Bitwise_Copy (To => To, From => Result);
end Not_Constrained;

Maybe I'm still missing something. Anyway, I never thought user-defined 
assignment was a real need. Limited types and Assign procedures seem adequate to 
me. What Ada 83 really lacked was finalization. While I'm glad Ada 95 has it, I 
think I'd prefer

for R'Finalize use ...;

-- 
Jeff Carter
"English bed-wetting types."
Monty Python & the Holy Grail
15



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-12-06 20:43                           ` Dmitry A. Kazakov
@ 2005-12-07  2:00                             ` Jeffrey R. Carter
  2005-12-07 10:01                               ` Dmitry A. Kazakov
  0 siblings, 1 reply; 37+ messages in thread
From: Jeffrey R. Carter @ 2005-12-07  2:00 UTC (permalink / raw)


Dmitry A. Kazakov wrote:

> I think that a more general approach could be to have some naming
> convention for the base type. When a type is composed by some predefined
> operation like type ... is record I wished to have a name of the subtype
> that still has all predefined operations. Maybe this name should be only
> visible in the private part of the unit.

I can see how that would be useful.

> But what about the following problems / issues:

IANALL and I haven't thought deeply about this, but I think these can be solved.

-- 
Jeff Carter
"English bed-wetting types."
Monty Python & the Holy Grail
15



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-12-07  2:00                             ` Jeffrey R. Carter
@ 2005-12-07 10:01                               ` Dmitry A. Kazakov
  0 siblings, 0 replies; 37+ messages in thread
From: Dmitry A. Kazakov @ 2005-12-07 10:01 UTC (permalink / raw)


On Wed, 07 Dec 2005 02:00:58 GMT, Jeffrey R. Carter wrote:

> IANALL and I haven't thought deeply about this, but I think these can be solved.

Sure, but not in a model where assignment is treated as just a plain
subroutine to override. We cannot override the assignments of the
components. There should be a way for the type designer to define things to
be done on each assignment no matter what the derived type could do. There
should be private and public parts of assignment and so on and so far.

-- 
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-12-07  1:57                             ` Jeffrey R. Carter
@ 2005-12-08  0:50                               ` Randy Brukardt
  2005-12-08 19:37                                 ` Jeffrey R. Carter
  0 siblings, 1 reply; 37+ messages in thread
From: Randy Brukardt @ 2005-12-08  0:50 UTC (permalink / raw)


"Jeffrey R. Carter" <spam@spam.com> wrote in message
news:ekrlf.267$Tg2.247@newsread1.news.pas.earthlink.net...
> Randy Brukardt wrote:
> >
> > For an another explanation of this, see ARM 7.6(17.a-17.h).
> > http://www.adaic.com/standards/95aarm/html/AA-A-5-3.html
>
> Is it meaningful that you reference 7.6 and provide the URL of 5.3?

No, cut-and-paste error.

> > The other issues are solveable, but this one is not.
>
> This still doesn't seem like a problem to me. Suppose R is a discriminated
> record with default:
>
> type R (D : D_Type := D_Type'First) is record
>     ...
> end record;
>
> and we want to write Assign:
>
> procedure Assign (To : in out R; From : in R);

That's not the problem. The problem is with the components of R, if they
also have user-defined assignment.

These user-defined assignments have to compose (otherwise, you'd be breaking
the invariants of the component types - remember, these components are
likely private types, and you might not have any idea how they're
implemented). So, you have to be able to *automatically* do the right thing
for each component. (This, BTW, is why Ada insists that an exception in one
Adjust routine be delayed until all other Adjusts have completed -- we don't
want a failure in one abstraction to destroy another, unrelated one.)

Say you have an assignment for type R as described above, and a function F
returning an object of type R. And you have type S defined as:

type S (D : Boolean := False) is record
    case D is
        when False => null;
        when True => C : R;
    end case;
end record;

O : S; -- D = False here.

O := (D => True, C => F);

Now, how is this assignment performed if we're using the default assignment
here? Since we need to component, we need to call the Assign procedure on
the component C, but what left-hand side to pass as To? There isn't a
component O.C in the left-hand side!

Now, you could try to (a) require this also have a user-defined Assign [but
that's very unfriendly and error-prone] or (b) ban components that have
user-defined assignment from being discriminant dependent [but this would be
a big contract model problem - or, a lot of things that are currently done
in generic bodies could no longer be. For instance, if R was a generic
private type, the above type S would have to be illegal in a generic body -
not matter what the actual type of R is.]

So there is no solution in the framework of Ada. To solve the problem, you'd
have to get rid of discriminants and discriminant-dependent components --
and that's not an option for Ada.

...
> Maybe I'm still missing something. Anyway, I never thought user-defined
> assignment was a real need. Limited types and Assign procedures seem
adequate to
> me. What Ada 83 really lacked was finalization. While I'm glad Ada 95 has
it, I
> think I'd prefer
>
> for R'Finalize use ...;

Maybe Ada 200Y limited types and Assign procedures would be adequate, but
certainly not the Ada 95 variety. Ada 95 limited types don't allow (1)
aggregates; (2) constants; (3) useful functions; or (4) any sort of complex
initialization. Which means that you can't use many of the techniques that
help reduce bugs in Ada (such as letting the compiler check that all
components have been given in an aggregate). And limited types also block
most optimizations by their very nature. That's useful in some cases, but in
others you'd rather let the compiler eliminate extra temporaries and
Finalizes. (That's allowed for non-limited types, but never for limited
types.)

                           Randy.







^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-12-08  0:50                               ` Randy Brukardt
@ 2005-12-08 19:37                                 ` Jeffrey R. Carter
  2005-12-09  2:36                                   ` Randy Brukardt
  0 siblings, 1 reply; 37+ messages in thread
From: Jeffrey R. Carter @ 2005-12-08 19:37 UTC (permalink / raw)


Randy Brukardt wrote:

> Now, how is this assignment performed if we're using the default assignment
> here? Since we need to component, we need to call the Assign procedure on
> the component C, but what left-hand side to pass as To? There isn't a
> component O.C in the left-hand side!

I guess I'm still missing something. Default assignment to a record with 
discriminants, or at least that changes the discriminants, invoking 'Assign for 
a component, would assign to an intermediate with the correct discriminants, 
which is then copied into the target as is currently done.

Working out the rules is probably complicated, and hardly worth the effort since 
Ada uses another mechanism, but I'm still not convinced it couldn't be done.

Probably because IANALL and I'm missing something.

> Maybe Ada 200Y limited types and Assign procedures would be adequate, but
> certainly not the Ada 95 variety. Ada 95 limited types don't allow (1)
> aggregates; (2) constants; (3) useful functions; or (4) any sort of complex
> initialization. Which means that you can't use many of the techniques that
> help reduce bugs in Ada (such as letting the compiler check that all
> components have been given in an aggregate). And limited types also block
> most optimizations by their very nature. That's useful in some cases, but in
> others you'd rather let the compiler eliminate extra temporaries and
> Finalizes. (That's allowed for non-limited types, but never for limited
> types.)

I've always felt that preventing the use of aggregates was an important part of 
limited types. Constants don't seem like a problem, since they can be 
implemented as functions. You can pass function results to the From parameter of 
Assign.

Complex default initialization requires some work, but I was able to do some 
pretty complex default initialization using functions as default initial values 
in Ada 83. Preventing user-defined initialization seems like an important part 
of limited types.

-- 
Jeff Carter
"Why don't you bore a hole in yourself and let the sap run out?"
Horse Feathers
49



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-12-08 19:37                                 ` Jeffrey R. Carter
@ 2005-12-09  2:36                                   ` Randy Brukardt
  2005-12-09  6:33                                     ` Jeffrey R. Carter
  0 siblings, 1 reply; 37+ messages in thread
From: Randy Brukardt @ 2005-12-09  2:36 UTC (permalink / raw)


"Jeffrey R. Carter" <spam@spam.com> wrote in message
news:xX%lf.800$n1.254@newsread2.news.pas.earthlink.net...
> Randy Brukardt wrote:
>
> > Now, how is this assignment performed if we're using the default
assignment
> > here? Since we need to component, we need to call the Assign procedure
on
> > the component C, but what left-hand side to pass as To? There isn't a
> > component O.C in the left-hand side!
>
> I guess I'm still missing something. Default assignment to a record with
> discriminants, or at least that changes the discriminants, invoking
'Assign for
> a component, would assign to an intermediate with the correct
discriminants,
> which is then copied into the target as is currently done.

You're expecting a bitwise copy to work for that? That seems wrong; since
the advantage to your scheme is that you can do operations on the old
left-hand side before the assignment. But if that "old" object is a
temporary, you have no access to it, and you have degraded to the current
scheme. Except that it is worse, because you can't have position-dependent
items.

What I mean by the latter is something that depends on the actual object,
such as 'Access of it. Claw depends on that heavily; Adjust and Finalize
updates or removes the pointers from the various internal lists. That
doesn't work if the LHS isn't the final one (at least one compiler made that
mistake early on in the Ada 95 process).

> Working out the rules is probably complicated, and hardly worth the effort
since
> Ada uses another mechanism, but I'm still not convinced it couldn't be
done.
>
> Probably because IANALL and I'm missing something.

Probably because what you're suggesting would be less useful than Adjust is,
if it had no guarateed access to the LHS *and* it didn't allow self-pointers
and the like.

> > Maybe Ada 200Y limited types and Assign procedures would be adequate,
but
> > certainly not the Ada 95 variety. Ada 95 limited types don't allow (1)
> > aggregates; (2) constants; (3) useful functions; or (4) any sort of
complex
> > initialization. Which means that you can't use many of the techniques
that
> > help reduce bugs in Ada (such as letting the compiler check that all
> > components have been given in an aggregate). And limited types also
block
> > most optimizations by their very nature. That's useful in some cases,
but in
> > others you'd rather let the compiler eliminate extra temporaries and
> > Finalizes. (That's allowed for non-limited types, but never for limited
> > types.)
>
> I've always felt that preventing the use of aggregates was an important
part of
> limited types.

That seems odd to me; what's the value of preventing aggregates of types
containing components of the limited type? (Preventing aggregates of limited
private types is a feature of private types, not limited types.)

> Constants don't seem like a problem, since they can be
> implemented as functions. You can pass function results to the From
parameter of
> Assign.

Not in Ada 95; you can't write a useful function without standing on your
head. Such a function can only return a global variable, which is ugly at
best and impossible for anything but literal constants.

> Complex default initialization requires some work, but I was able to do
some
> pretty complex default initialization using functions as default initial
values
> in Ada 83. Preventing user-defined initialization seems like an important
part
> of limited types.

Again, you seem to be confusing features that belong to other things
(private types and the <> discriminant) with limited types. You can prevent
user-defined initialization in other ways, so it's not necessary to make
that a feature of limited types.

The *only* thing limited types are about is preventing copying. Limited
objects are supposed to be always built-in place. That's important, because
in order to get that one feature (no copying), it's nasty to get a passel of
other things that you may or may not want. And if you don't want those other
things, you often have to abandon limited types. (The major reason that most
objects are non-limited in Claw is that limited functions aren't useful in
Ada 95.)

We've tried to eliminate unnecessary restrictions on the categories of
types, so that it is possible to pick categories based on their main
properties - with as few surprises as possible.

                              Randy.







^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-12-09  2:36                                   ` Randy Brukardt
@ 2005-12-09  6:33                                     ` Jeffrey R. Carter
  2005-12-09 20:35                                       ` Randy Brukardt
  0 siblings, 1 reply; 37+ messages in thread
From: Jeffrey R. Carter @ 2005-12-09  6:33 UTC (permalink / raw)


Randy Brukardt wrote:
> 
> You're expecting a bitwise copy to work for that? That seems wrong; since
> the advantage to your scheme is that you can do operations on the old
> left-hand side before the assignment. But if that "old" object is a
> temporary, you have no access to it, and you have degraded to the current
> scheme. Except that it is worse, because you can't have position-dependent
> items.

I'm not being very clear or precise, am I? We're talking about default 
assignment for a type with a component with user-defined assignment, but the 
component doesn't exist in the LHS. If that's the only component with 
user-defined assignment, then you can't use the LHS during assignment anyway, so 
you can assign to an intermediate for the whole value without losing anything.

If you have components with user-defined assignment in the LHS, and they 
continue to exist after the discriminants change, then you would use the 
components themselves. If you have a mix, then you can use intermediates only 
for the objects that don't already exist in the LHS.

We've probably beaten this dead horse too much already.

> That seems odd to me; what's the value of preventing aggregates of types
> containing components of the limited type? (Preventing aggregates of limited
> private types is a feature of private types, not limited types.)
> 
> Not in Ada 95; you can't write a useful function without standing on your
> head. Such a function can only return a global variable, which is ugly at
> best and impossible for anything but literal constants.

I think we're talking at cross purposes. I'm talking about changes that I 
thought Ada 83 needed. You're talking about how things work in Ada 95. In Ada 
83, the only limited types were tasks and private types (and types with limited 
components), and for the most part I'm thinking of limited private types that 
are not limited in the full view.

-- 
Jeff Carter
"Why don't you bore a hole in yourself and let the sap run out?"
Horse Feathers
49



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-12-09  6:33                                     ` Jeffrey R. Carter
@ 2005-12-09 20:35                                       ` Randy Brukardt
  2005-12-10  7:53                                         ` Jeffrey R. Carter
  0 siblings, 1 reply; 37+ messages in thread
From: Randy Brukardt @ 2005-12-09 20:35 UTC (permalink / raw)


"Jeffrey R. Carter" <spam@spam.com> wrote in message
news:6z9mf.1704$Tg2.1265@newsread1.news.pas.earthlink.net...
> Randy Brukardt wrote:
> >
> > You're expecting a bitwise copy to work for that? That seems wrong;
since
> > the advantage to your scheme is that you can do operations on the old
> > left-hand side before the assignment. But if that "old" object is a
> > temporary, you have no access to it, and you have degraded to the
current
> > scheme. Except that it is worse, because you can't have
position-dependent
> > items.
>
> I'm not being very clear or precise, am I? We're talking about default
> assignment for a type with a component with user-defined assignment, but
the
> component doesn't exist in the LHS. If that's the only component with
> user-defined assignment, then you can't use the LHS during assignment
anyway, so
> you can assign to an intermediate for the whole value without losing
anything.
>
> If you have components with user-defined assignment in the LHS, and they
> continue to exist after the discriminants change, then you would use the
> components themselves. If you have a mix, then you can use intermediates
only
> for the objects that don't already exist in the LHS.

OK, that works, but it's useless (which is what I'm trying to point out). If
Assign might be called with a real LHS, or a fake LHS, then there isn't much
that you can practically do with the LHS in the Assign routine.

And, in any case, your model assumes component-by-component assignment,
while the Ada model is full object assignment. (That is, the discriminants
and components all change together.) What happens if the assignment fails?
It's important that the components that are disappearing are finalized.

Finally, the code you are suggesting would be very nasty; the assignment
would operate differently depending on the discriminant values of the LHS,
and the tests needed could be very complex.

> We've probably beaten this dead horse too much already.

Probably true. I guess my point is that this can't really be done, because
any way to make it work would be so complex so as to be impossible to get
right or understand. And such complexity usually leads to making it too hard
to use for the user of it as well.

> > That seems odd to me; what's the value of preventing aggregates of types
> > containing components of the limited type? (Preventing aggregates of
limited
> > private types is a feature of private types, not limited types.)
> >
> > Not in Ada 95; you can't write a useful function without standing on
your
> > head. Such a function can only return a global variable, which is ugly
at
> > best and impossible for anything but literal constants.
>
> I think we're talking at cross purposes. I'm talking about changes that I
> thought Ada 83 needed. You're talking about how things work in Ada 95. In
Ada
> 83, the only limited types were tasks and private types (and types with
limited
> components), and for the most part I'm thinking of limited private types
that
> are not limited in the full view.

Gee, I've done my best to forget that Ada 83 existed. We're on the second
(or third, depending on how you count) major update since then.

Limited private types that aren't limited in the full view are, for most
purposes, the same as non-limited types. Indeed, they're arguably a mistake
in the language (types that change limitedness are a continuing headache
with the semantics of Ada). What's interesting is the types that are
position-dependent, or are "really limited" -- these should never be copied,
even inside of their full definition.

In any case, being able to write with the natural syntax of ":=" is why
people want user-defined assignment, and it's the same reason that people
want initialization to work for limited types. (Unfortunately, ":=" is
overloaded in Ada, and initialization and assignment are completely
different. Ada 83 screwed up when it treated them as the same for limited
types, and we're finally fixing that now.)

                                 Randy.

                                Randy.






^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: Controlled types and exception safety
  2005-12-09 20:35                                       ` Randy Brukardt
@ 2005-12-10  7:53                                         ` Jeffrey R. Carter
  0 siblings, 0 replies; 37+ messages in thread
From: Jeffrey R. Carter @ 2005-12-10  7:53 UTC (permalink / raw)


Randy Brukardt wrote:

> Probably true. I guess my point is that this can't really be done, because
> any way to make it work would be so complex so as to be impossible to get
> right or understand. And such complexity usually leads to making it too hard
> to use for the user of it as well.

Quite possibly. I agree that it gets more complex the deeper you get into it, 
and that's usually A Bad Thing. But complex and impossible are 2 different things.

I have a better understanding of the objections to this approach now. Thanks for 
that.

> Gee, I've done my best to forget that Ada 83 existed. We're on the second
> (or third, depending on how you count) major update since then.

I've always thought Ada 83 was a pretty good language, but did have things that 
needed improvement. I've already mentioned finalization; better 
low-level/bit-twiddling abilities and tightening the generic contract model are 
a couple of other obvious things.

Anyway, the discussion we've just finished having only makes sense to me as 
being about changes to Ada 83 other than those that Ada 95 made; alternatives to 
the Ada-95 way. Talking about them as changes to Ada 95 is meaningless; Ada 95 
choose a different path.

So, talking about finalizing components during assignment is meaningless, since 
Ada 83 didn't have finalization, and we haven't defined what, if any, form of 
finalization occurs in this alternative revision we were discussing.

> Limited private types that aren't limited in the full view are, for most
> purposes, the same as non-limited types. Indeed, they're arguably a mistake
> in the language (types that change limitedness are a continuing headache
> with the semantics of Ada). What's interesting is the types that are
> position-dependent, or are "really limited" -- these should never be copied,
> even inside of their full definition.

I've always found it very important for a package to be able to control what a 
client can do with its abstraction, and making the public view limited is a 
useful tool for achieving that.

-- 
Jeff Carter
"I like it when the support group complains that they have
insufficient data on mean time to repair bugs in Ada software."
Robert I. Eachus
91



^ permalink raw reply	[flat|nested] 37+ messages in thread

end of thread, other threads:[~2005-12-10  7:53 UTC | newest]

Thread overview: 37+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2005-11-30 13:57 Controlled types and exception safety Maciej Sobczak
2005-11-30 15:06 ` Dmitry A. Kazakov
2005-11-30 16:19   ` Maciej Sobczak
2005-12-01  0:05     ` Stephen Leake
2005-12-01  9:21     ` Dmitry A. Kazakov
2005-12-01 10:46       ` Maciej Sobczak
2005-12-01 15:08         ` Dmitry A. Kazakov
2005-12-02  4:17           ` Randy Brukardt
2005-12-02  9:29             ` Maciej Sobczak
2005-12-02 18:12               ` tmoran
2005-12-02 19:15                 ` Robert A Duff
2005-12-02 21:42                   ` tmoran
2005-12-06  9:00                     ` Maciej Sobczak
2005-12-06  9:50                       ` Dmitry A. Kazakov
2005-12-06 18:34                         ` Jeffrey R. Carter
2005-12-06 19:34                           ` Randy Brukardt
2005-12-06 21:20                             ` Dmitry A. Kazakov
2005-12-07  1:57                             ` Jeffrey R. Carter
2005-12-08  0:50                               ` Randy Brukardt
2005-12-08 19:37                                 ` Jeffrey R. Carter
2005-12-09  2:36                                   ` Randy Brukardt
2005-12-09  6:33                                     ` Jeffrey R. Carter
2005-12-09 20:35                                       ` Randy Brukardt
2005-12-10  7:53                                         ` Jeffrey R. Carter
2005-12-06 20:43                           ` Dmitry A. Kazakov
2005-12-07  2:00                             ` Jeffrey R. Carter
2005-12-07 10:01                               ` Dmitry A. Kazakov
2005-12-02 23:21             ` Robert A Duff
2005-11-30 17:46 ` Jean-Pierre Rosen
2005-11-30 21:02 ` Jeffrey R. Carter
2005-11-30 22:06   ` Björn Persson
2005-11-30 23:52     ` Randy Brukardt
2005-12-01  5:26     ` Jeffrey R. Carter
2005-12-02 23:51       ` Robert A Duff
2005-12-06 11:41   ` Peter C. Chapin
2005-12-06 12:50     ` Jean-Pierre Rosen
2005-12-06 13:06     ` Dmitry A. Kazakov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox