April 03, 2008
Sean Kelly wrote:
>> Essentially, const in C++ only works for
>> trivial objects. (I'm not saying trivial objects are not useful, I'm
>> just saying they are trivial.)
> 
> I think this statement is open to interpretation.  From a theoretical perspective
> I agree.  But with RAII objects managing references and the like, it's rare for
> even very complex objects to contain raw pointers, and such RAII objects can
> be made to fake transitivity of const by overloading their const and non-const
> methods appropriately.

For a new design, we shouldn't have to fake it. And faking it is complicated, which means that realistically, it isn't going to get done. People will just rely on convention.
April 03, 2008
== Quote from Walter Bright (newshound1@digitalmars.com)'s article
> Invariants won't remove the need for mutexes and the like, but invariants themselves won't need to be wrapped inside mutexes. Furthermore, accessing invariants won't need to consider memory fences and order of evaluation issues.

I don't know how to view topics as threaded with this web client so I'll assume you're responding to me.  From the above, does this mean that you're taking back what you said previously:

> If C.name were invariant, there would be no need for any locks. I don't think this is a good example, because with D's invariant strings there is no need for such locking.

Also, I believe my example demonstrated that memory fences and order
of evaluation issues are still a concern with invariants.  They aren't a
magical safe ticket to lock-free programming land.

Let me re-iterate my point by saying that the /only/ feature provided by invariants is that once invariant data is shared through a properly synchronized method, the invariant is guaranteed not to change out from under the viewer.  This is identical to the guarantee provided by returning a copy of the same data when it is requested.  Invariant references simply guarantee that any copying will be done up front, automatically, so it does not have to occur manually later on.

In general, I feel that the amount of copying caused by invariants is greater
than without them in a typical program, but invariants have the advantage
of making this copying automatic.  Java strings, for example, are ostensibly
invariant (even though this can be subverted by the clever programmer), so
experience with them there can be directly applied to invariant strings in D
(Java memory model notwithstanding).


Sean
April 03, 2008
"Janice Caron" wrote
> On 03/04/2008, Steven Schveighoffer wrote:
>>  For example, if I have a function that looks like this:
>>
>>  int[] calculateSomeValues(const(Calculator) c);
>>
>>  written by some other developer who says "I won't be changing c, and c
>>  defines that f is const, so I'll declare that c is const".
>
> The difference between Calculator and CachingCalculator is a bit like the difference between File and BufferedFile. We all /know/ that file access is slow, and so buffering speeds things up. Ditto here. That other developer should have written
>
>    int[] calculateSomeValues(CachingCalculator c);
>
> and if they were dumb enough to use an underpowered class, then don't call their function.

They did not write CachingCalculator, I did.  They have Calculator, I wanted to make a derived Calculator that uses caching and pass it into the function.  What if the function simply took an interface?  The issue is that the developer of that function is promising not to change c, which he doesn't.  He cannot know how I will want to implement c.  In fact, given the current const regime, the only correct choice in this is to NOT use const on Calculator.f and therefore on any functions that take the calculator, as it imposes too many restrictions on whoever is developing the type that will be passed in.

And the "don't call badly defined functions" scheme is not always possible.

>
>
>
>>  and inevitably, they [other people] will have
>>  incorrectly implemented it
>
> One doesn't introduce new language features on the assumption that other people will incorrectly implement other language features.

I'm not introducing a 'new' language feature.  Logical const is already available through workarounds.  I'm suggesting we make it easier to express logical const than by the hackish workarounds I've posted.

> It's just education, that's all. When using D, you program "the D way". And that means, you don't declare something const, if you know that bits of it are going to change. That's how it works in D. Look at it like this - D mandates good habits.

D const mandates workarounds, which I do not consider to be good habits. Any time a language feature that is supposed to help developers work together makes the language harder to work with, that's a red flag to me. I'm not saying I disagree with const, I'm just saying its absolute transitivity is not preventing us from using logical const-like features, and so the notion that it is required, or at least the notion that logical const won't work, is false.

-Steve


April 03, 2008
On 03/04/2008, Steven Schveighoffer <schveiguy@yahoo.com> wrote:
> They did not write CachingCalculator, I did.  They have Calculator, I wanted
>  to make a derived Calculator that uses caching and pass it into the
>  function.  What if the function simply took an interface?  The issue is that
>  the developer of that function is promising not to change c, which he
>  doesn't.  He cannot know how I will want to implement c.  In fact, given the
>  current const regime, the only correct choice in this is to NOT use const on
>  Calculator.f and therefore on any functions that take the calculator, as it
>  imposes too many restrictions on whoever is developing the type that will be
>  passed in.

Fair point.


> I'm not introducing a 'new' language feature.  Logical const is already
>  available through workarounds.  I'm suggesting we make it easier to express
>  logical const than by the hackish workarounds I've posted.

Presumably though, what you call a "hackish workaround" is exactly what the compiler would have to implement for you if you got what you want. That is,

    class C
    {
        mutable M m;
    }

would just be syntactic sugar for

    class C
    {
        private static M[const(C)] __cache;
        /* and appropriate functions to make it work */
    }

Maybe it's better to have all that explicit, rather than hidden, so that it becomes obvious there's a price to pay, and that the lookup time for an AA had better be insignificant compared to the cost of computing an M or it's not worth it.
April 03, 2008
"Janice Caron" wrote
> On 03/04/2008, Steven Schveighoffer wrote:
>> I'm not introducing a 'new' language feature.  Logical const is already
>>  available through workarounds.  I'm suggesting we make it easier to
>> express
>>  logical const than by the hackish workarounds I've posted.
>
> Presumably though, what you call a "hackish workaround" is exactly what the compiler would have to implement for you if you got what you want. That is,
>
>    class C
>    {
>        mutable M m;
>    }
>
> would just be syntactic sugar for
>
>    class C
>    {
>        private static M[const(C)] __cache;
>        /* and appropriate functions to make it work */
>    }
>
> Maybe it's better to have all that explicit, rather than hidden, so that it becomes obvious there's a price to pay, and that the lookup time for an AA had better be insignificant compared to the cost of computing an M or it's not worth it.

This isn't what I'm requesting.  I'm requesting that

class C
{
    mutable M m;
}

Mean exactly what it says, that there is a piece of data stored with the class that is always mutable (to get nitpicky, I don't like the keyword mutable, as it implies that everything reachable through m is mutable, which it may not be).  I'm asking for the compiler to treat it as not part of the object state, *as if* it were a variable outside the class, in terms of const.  Think of it as an extra argument to all member functions that is not colored with the constancy of the member function.

There is no need for the compiler to implement my hack, I can do that easily enough with a mixin :)  If that was the problem, I would have never started this thread.

-Steve


April 03, 2008
On 03/04/2008, Steven Schveighoffer <schveiguy@yahoo.com> wrote:
> Mean exactly what it says, that there is a piece of data stored with the
>  class that is always mutable (to get nitpicky, I don't like the keyword
>  mutable, as it implies that everything reachable through m is mutable, which
>  it may not be).  I'm asking for the compiler to treat it as not part of the
>  object state, *as if* it were a variable outside the class, in terms of
>  const.  Think of it as an extra argument to all member functions that is not
>  colored with the constancy of the member function.

Perhaps there is a better solution. Perhaps, we could annotate functions which computationally expensive.

    class SuperIntensiveCalculator
    {
       int f(int x) const expensive
       {
            /* do really intense calculation */
       }
    }

I like that more. We simply give the compiler a hint that it might be worth caching the result.
April 03, 2008
"Janice Caron" wrote
> On 03/04/2008, Steven Schveighoffer wrote:
>> Mean exactly what it says, that there is a piece of data stored with the
>>  class that is always mutable (to get nitpicky, I don't like the keyword
>>  mutable, as it implies that everything reachable through m is mutable,
>> which
>>  it may not be).  I'm asking for the compiler to treat it as not part of
>> the
>>  object state, *as if* it were a variable outside the class, in terms of
>>  const.  Think of it as an extra argument to all member functions that is
>> not
>>  colored with the constancy of the member function.
>
> Perhaps there is a better solution. Perhaps, we could annotate functions which computationally expensive.
>
>    class SuperIntensiveCalculator
>    {
>       int f(int x) const expensive
>       {
>            /* do really intense calculation */
>       }
>    }
>
> I like that more. We simply give the compiler a hint that it might be worth caching the result.

This might solve this particular problem, but there are other reasons to have logical const types that are not solved this way.

-Steve


April 03, 2008
== Quote from Janice Caron (caron800@googlemail.com)'s article
> On 02/04/2008, Sean Kelly <sean@invisibleduck.org> wrote:
> > My traditional argument in support of logical const is this:
> >
> >     class C
> >     {
> >         mutable mutex monitor;
> >         <snip>
> >     };
> But in D, the class C will already have a mutex, by virtue of the fact that it derives from Object.

Yup.  I did mention object-local logs or other similar things, but mutexes are far and away my most common use of mutable in C++.  So no worries here in D.

> ...which raises an interesting point. Can "synchronized" be used on a const object? What about an invariant object?

They'll work just fine with const objects in D.  Object monitors in D currently live outside the const checking scheme.


Sean
April 03, 2008
"Sean Kelly" wrote
> == Quote from Janice Caron article
>> ...which raises an interesting point. Can "synchronized" be used on a const object? What about an invariant object?
>
> They'll work just fine with const objects in D.  Object monitors in D
> currently
> live outside the const checking scheme.

Oh so you mean Walter couldn't implement mutexes without logical const huh? I think this part of the system should be ripped out and you should have to pass mutexes along with objects everywhere you want to ensure thread safety and const is transitive.  That's easier and less obfuscated, right?

-Steve


April 03, 2008
Steven Schveighoffer Wrote:

> "Sean Kelly" wrote
> > == Quote from Janice Caron article
> >> ...which raises an interesting point. Can "synchronized" be used on a const object? What about an invariant object?
> >
> > They'll work just fine with const objects in D.  Object monitors in D
> > currently
> > live outside the const checking scheme.
> 
> Oh so you mean Walter couldn't implement mutexes without logical const huh? I think this part of the system should be ripped out and you should have to pass mutexes along with objects everywhere you want to ensure thread safety and const is transitive.  That's easier and less obfuscated, right?
> 
> -Steve
> 
> 

The whole point of invariant objects and pure functions in terms of thread safety is that you don't need any form of synchronized data access. If, for instance a piece of data is guaranteed not to change, then two cores can hold two separate copies of the same data in each of their local caches instead of having to fetch the same data from an external source. Requiring synchronization on all const objects would entirely defeat optimizations like these.

The end result is, don't use const, invariant, or pure if you plan on modifying internal data on an object. A caching calculator, for instance, is inherently not thread safe, because one thread might cause the calculator to write to the cache at the same time another thread reads from it, so the calculator should never be allowed to be const or invariant. If I understand correctly, Walter wants the compiler to be able to make assumptions on an invariant object that would not be possible if it had mutable members.

I think that the ideas of pure and invariant will be very valuable in the end and one of D's strong selling points. It will require more careful library design though, and some designs and features, such as caching calculations or synchronizing memory access, will simply not be usable with it.