April 02, 2008
On 02/04/2008, Steven Schveighoffer <schveiguy@yahoo.com> wrote:
>  > (2) are we really sure that modifying an AA is an atomic operation? I'm
>  > not.
>
> I am really sure that modifying an AA is not an atomic operation, but that
>  has no bearing on the proof.  Setting x in the mutable version is also not
>  atomic.

Two instances of the same muty class would each have their own independent mutable variables. That means that modifications to those variables don't have to be atomic.

However, two instances of the same globby class would share the /same/ AA, so accesses to that AA would need to be atomic, otherwise, the AA could itself end up with a corrupt memory layout.
April 02, 2008
"Janice Caron" wrote
> On 02/04/2008, Steven Schveighoffer wrote:
>>  > (2) are we really sure that modifying an AA is an atomic operation?
>> I'm
>>  > not.
>>
>> I am really sure that modifying an AA is not an atomic operation, but
>> that
>>  has no bearing on the proof.  Setting x in the mutable version is also
>> not
>>  atomic.
>
> Two instances of the same muty class would each have their own independent mutable variables. That means that modifications to those variables don't have to be atomic.
>
> However, two instances of the same globby class would share the /same/ AA, so accesses to that AA would need to be atomic, otherwise, the AA could itself end up with a corrupt memory layout.

OK, sure, so we lock the AA with a mutex lock :)

Again, has no bearing on the proof.

-Steve


April 02, 2008
Steven Schveighoffer wrote:
> "Janice Caron" wrote in message

> The point is that logical const is still possible, even with transitive const, because the global namespace is not const.  There is no way around this except with pure functions.  Which I think you agree.
> 
> HOWEVER, the point that everyone is arguing is why does logical const make pure functions or functional programming impossible?  Clearly, it is ALREADY POSSIBLE to have logical const, and clearly, pure functions are possible! I'm saying transitive const is mathematically equivalent to logical const ALREADY.  Please try and grasp that concept.

Globals are a loophole.  Pure will close that loophole.  Allowing mutable members would be another loophole.  Maybe pure can just close that one too?  It would have to check that any access to a const object does not touch mutable fields.  It seems possible.  Would it be impossible for the compiler to check that for some reason?  Probably pure functions will only be able to call other pure functions, so that rules out the possibility of indirectly accessing mutable data via a function or method call.  So the only case that needs to be checked is directly referencing such a mutable field.

It does seem like something that could be tacked on after the rest of const/invariant/pure is done, though.  Much like C++'s mutable was.

--bb
April 02, 2008
"Bill Baxter" wrote
> Steven Schveighoffer wrote:
>> "Janice Caron" wrote in message
>
>> The point is that logical const is still possible, even with transitive const, because the global namespace is not const.  There is no way around this except with pure functions.  Which I think you agree.
>>
>> HOWEVER, the point that everyone is arguing is why does logical const make pure functions or functional programming impossible?  Clearly, it is ALREADY POSSIBLE to have logical const, and clearly, pure functions are possible! I'm saying transitive const is mathematically equivalent to logical const ALREADY.  Please try and grasp that concept.
>
> Globals are a loophole.  Pure will close that loophole.  Allowing mutable members would be another loophole.  Maybe pure can just close that one too?  It would have to check that any access to a const object does not touch mutable fields.  It seems possible.  Would it be impossible for the compiler to check that for some reason?  Probably pure functions will only be able to call other pure functions, so that rules out the possibility of indirectly accessing mutable data via a function or method call.  So the only case that needs to be checked is directly referencing such a mutable field.

Thanks Bill, this is exactly what I'm trying to say :)

> It does seem like something that could be tacked on after the rest of const/invariant/pure is done, though.  Much like C++'s mutable was.

Absolutely, but I'd at least like Walter to stop saying that transitive const (read, transitive *keyword* const) is neccessary for pure functions :) If he says "Oh yeah, I guess transitive const isn't necessary, but it's not a priority to fix it right now", then I'd be happy.

-Steve


April 02, 2008
Janice Caron wrote:

> 
>>  - a pure method cannot access the mutable portion of a logically invariant
>>  data value.
> 
> Well, the easy way to do that is to redesign the class into two
> classes, one const and the other not. That way, you get the exact same
> benefit, but in addition, you end up saying what you mean, instead of
> writing obfuscated code.

I don't understand how to do what you're saying.  Here's the version using mutable:

class Calc
{
   private mutable int result_cache;
   int calc_result(int) { .../*uses result_cache to save results*/ }
   ...
}
auto x = new Calc;
int y = x.calc_result(32);

So how do you separate that into two classes?  How do I create an instance of that two-class thing?  Via a third class?  How can Calc access its cache?  The user has to pass in the cache to every call to calc_result?   The goal is for the caching to be an implementation detail that users need not worry about.  The above seems about as straightforward as it gets.  Any else is probably going to have to come out *more* obfuscated.

--bb
April 02, 2008
"Bill Baxter" wrote
> Janice Caron wrote:
>
>>
>>>  - a pure method cannot access the mutable portion of a logically
>>> invariant
>>>  data value.
>>
>> Well, the easy way to do that is to redesign the class into two classes, one const and the other not. That way, you get the exact same benefit, but in addition, you end up saying what you mean, instead of writing obfuscated code.
>
> I don't understand how to do what you're saying.  Here's the version using mutable:
>
> class Calc
> {
>    private mutable int result_cache;
>    int calc_result(int) { .../*uses result_cache to save results*/ }
>    ...
> }
> auto x = new Calc;
> int y = x.calc_result(32);
>
> So how do you separate that into two classes?  How do I create an instance of that two-class thing?  Via a third class?  How can Calc access its cache?  The user has to pass in the cache to every call to calc_result? The goal is for the caching to be an implementation detail that users need not worry about.  The above seems about as straightforward as it gets. Any else is probably going to have to come out *more* obfuscated.

I don't think you can even wrap the class, because what about this:

class CachedCalc
{
   Calc c;
   int result_cache;
}

class ConstCalc
{
   const(Calc) c;
   int result_cache;
}

This should work, but because ConstCalc and CachedCalc are not derived classes it doesn't:

ConstCalc cc = new CachedCalc();

So the only way to do it is to carry around both calc and the cache, and pass the cache in to every call that needs it.  I agree this is useless.

On top of that, you can't create a constructor that uses a CachedCalc, because you can't re-assign c, thanks to no tail-const class references...

-Steve


April 02, 2008
Walter Bright wrote:
> Sean Kelly wrote:
>> I agree that transitive const can achieve more demonstrably correct code
>> but I don't think it follows that this will necessarily improve productivity.
>> My experience with const in C++ is that it actually reduces productivity
>> because I spend more time dealing with the misapplication of const than
>> I gain from whatever bugs the application of const may have prevented.
>> In fact, in all my time as a programmer I can't think of a single instance
>> where I've encountered a bug that could have been prevented through
>> the use of const.
> 
> C++ const is more of a suggestion than anything verifiable by the compiler and static analysis. This will become more obvious as C++ tries to compete in the multiprogramming arena. The C++ compiler *cannot* help with multiprogramming issues; the C++ programmer is entirely dependent on visual inspection of the code, making C++ a tedious, labor intensive language for multiprogramming.
> 
> How many times have you looked at the documentation for a function API, and wondered which of its parameters were input and which were output?
> 
> When you talk about the "misapplication" of const, does that mean failure to understand what the specific API of a function is?

My guess is he means trying to call a function with a const value where the author forgot to put the const label on an argument.

So you go fix that function's signature.

And then find that 3 functions it calls also have incorrect signatures.

And then find 5 more that those call that are also incorrect.

Then you start thinking, wow, this library isn't const-correct at all, maybe I should just back out all my changes and cast away const at the top.  Or maybe I should slog it through and fix the library?

That kind of thing is all too familiar to C++ programmers, and quite annoying to deal with.   So it leaves an impression.


On the other hand, const preventing you from calling some method you shouldn't be calling (and thereby preventing a bug) doesn't leave much of an impression.  You just go "duh -- of course I can't call that" and move on.  Also bugs caused by data changing when you didn't expect it to don't usually jump out as due to lack of const.  Instead you go "doh! I shouldn't change that there" or "doh! I shouldn't call that function here".  So unfortunately I think the value of const is very difficult to judge using subjective measures.

But it's definitely somewhere in between "no use at all" and "absolutely required".  :-)

--bb
April 02, 2008
Janice Caron Wrote:

> On 02/04/2008, guslay <guslay@gmail.com> wrote:
> So apart from a little bit of terminology, we're agreeing.

Yes, it seems that we agree on a lot of things, yet reach different conclusions. I'm not sure yet how much of it is still due to misunderstandment, or just different expectations about the language.

I still think that some valid points have been raised by this thread, have not been answered. I understand that language design is about compromise, I just don't see what is the negative counterpart of allowing mutable. And I certainly don't see how claims such as (from the Const article):

"not having mutable facilitates code reviews"

and

"The problem with logical const is that const is no longer transitive. Not being transitive means there is the potential for threading race conditions..."

can be considered something other than gross overstatements in the lights of our discussion.

April 02, 2008
If you do away with transitive const, you cannot have invariant either. Const allows you to reuse code that works on invariants to work with mutables, too.

Logical const just utterly breaks the whole system. Every scheme we've looked at for logical const winds up in some way breaking invariants. If invariants are broken, the advantages of them all just go away. I suspect that logical const is like perpetual motion - if you think you've solved it, you've just made a mistake somewhere <g>. I also suspect that the failure of logical const validates the D scheme of invariants as being correct - there shouldn't be any holes in it.

You're right that invariant by itself is not enough to specify a pure function, a pure function also cannot read or write global state. But invariant is still a necessary condition, it's just not sufficient.

Peoples' troubles with const seem to stem from attempting to defeat it in one way or another. While defeating const is legal and trivial in C++, D tries to close those doors.
April 02, 2008
Janice Caron wrote:
> On 02/04/2008, Jason House <jason.james.house@gmail.com> wrote:
>> And how do global variables fit into that?  Technically, they're always
>>  reachable and never inherit const.
> 
> Global variables will /not/ be reachable from a pure function. Example:
> 
>     int g;
> 
>     pure int f()
>     {
>         return g; /*ERROR*/
>     }
> 
> The above will not compile, because global variables won't be
> available to pure functions.

That's right, unless they are invariant globals.