On Monday, 26 August 2013 at 06:14:02 UTC, Ali Çehreli wrote:
On 08/25/2013 05:16 AM, deadalnix wrote:

> The problem is that invariant are checked at the > beginning/end on public function calls. As a consequence, it > is impossible to use any public
> method in an invariant.

That's a very interesting observation. Could the solution be running the invariant only once, at the outermost public function call? Hm... It would have to be a runtime feature then, right? Every public function would have calls to the invariant but those calls would have to be elided at runtime. I think...

Here is another interesting observation: It is acceptable and quite normal that the object is in limbo state during a public member function. As a consequence, any function that operates on the object must use the object in a write-only fashion during that time frame. This is true even for non-member functions that the object is passed to. So, in theory, even a logging function cannot use the object. Hm...

If you will indulge a D newbie, lurker and former Eiffelist:

In design-by-contract theory, the invariant is part of the definition of a user-defined type. If type T has an invariant, an object t of that type is not a "real T" if it does not meet the invariant while publicly accessible (that is, while it is not in the hands of a member function of class T).

For instance, the purpose of the constructor is to establish the invariant -- some Eiffelists argue that this is the *sole* purpose of the constructor, and that constructors which perform initialization beyond that are overstepping their bounds.

For another point, T t should be capable of being tested for its invariant (and passing that test) anywhere and at any time it is publicly accessible. It is only a matter of convenience that implementations test the invariant at entry to and exit from public member functions of class T. That convenience relies on the guarantee that *only* T member functions can be allowed to modify the state of object t -- all other operations in the program must treat t as const (and can, of course, *rely* on t being const)... which, in turn, has all kinds of implications about what you can safely make public about a T object.

All of this has implications for D's contract guarantees. For instance, you may not design an invariant that fails on T.init (which is the state of t after you call t.clear() ). In other words, D's object model doesn't necessarily match strict DbC theory. And Jonathon Davis's notion of a t that "isn't really a T" but should be acceptable by T.opAssign() also falls outside the theory.

Offhand, I can think of two approaches that might address these desires without too badly weakening the invariant's guarantee:

+ attach an internal "depth gauge" to each T t. At entry to any public T member function, the depth gauge is incremented; at exit, decremented. Invariant is tested *only* when the depth gauge transitions from or to zero: that is, at its transition from "surfaced" (public) to "submerged" (in the hands of T class functions) or vice-versa. This would allow the implementation to elide any invariant tests while T member functions call other functions, even functions that are themselves public T member functions.

* an attribute which tags a public T function as accepting "broken Ts". Such a function would be expected to establish the invariant in the object, and test that invariant, at exit, but would *not* test the invariant at entry.

These two overlap but not entirely, and I'm still thinking about whether you would want both, or just one or the other. As I say, this is off the top of my head.

I hope I have contributed a useful notion, and not just muddied the waters. Thanks for listening.

-- Dai

Reply via email to