September 13, 2007
Janice Caron wrote:
> I've written a lot of multithreaded programming. A lot.

Ok. I've written some, enough to know it is very, very hard to get right. I also attended a conference of the top C++ people on what to do about multithreaded programming support in the C++ language, and came away from it with the realization that only a handful of those people even understood the problem (no, I don't count myself as one of them). These are the experts.


> (By the way - could you also allow the spelling "synchronised" for the
> benefit of speakers of British English?) :-)

Maybe. Colour, never. <g>


> synchronised is a blunt tool though. In my C++ code, I arrange it so
> that multiple threads can simultaneously obtain read-access, or
> exactly one thread can obtain write-access. Correct me if I'm wrong,
> but the built-in synchronized feature isn't that smart?

It's nothing more than a sugary mutex. It certainly isn't the overarching solution to multithreaded programming I had earlier thought it was.


> So anyway, as I mentioned further up this thread, I had this loopup
> class member function that got stuff from a file and cached it. I
> assure you it was thread-safe. It even allowed multiple readers (if
> the key/value pair was already cached), but if a lookup was not
> cached, one thread and one thread only got to open the file, read a
> chunk of data, and stick it in the file. This was a class that knew
> what it was doing.

If you can assure me it is thread safe, and it is, then you are a very rare programmer. Even if it is thread safe, it can still have deadlocks when used in conjunction with other thread safe packages. What I'm getting at is multithreaded programming is very, very hard. It needs to be simpler, a lot simpler.


> You're probably going to tell me that I can still do all that in D,
> providing I don't declare it const. The problem is, if I don't declare
> it const, /and/ all consts in D are transitive, then I can't store a
> reference to that object in any const structure. Basically I'd end up
> declaring just about nothing const, and I'd see no benefit.

You're right, I'm going to say that if it can change state, then it should not be declared const. I believe there is a greater benefit to things being reliably, checkably, constant, than there is for declaring things const that aren't constant.

I think what is pretty clear at this point is that const in D will be used differently than in C++.

>> Being in a module doesn't prevent another thread from using the module's
>> interface and thereby changing the state.
> Is that why you object?

That's one reason.


> I would have thought it the programmer's problem to ensure thread
> safety, not the compiler's. If I have a variable that's going to be
> accessed by multiple threads, then I'm going to make sure it's
> properly synchronised, and I if screw up, then it's my bug.

There are a couple problems with this:

1) Programmers aren't that good. Sure, a few are, but most of us aren't, and we write code we imagine is thread safe but really isn't. See Scott Meyer's article on double checked locking, for example.

2) Even if a programmer is that good, programmers still have bad days. It's why pilots have checklists for everything, and always use them, no matter how well they've memorized them.

3) Programmers being good doesn't help the code auditor, who has to always assume the worst.

4) Automation to detect flaws is better than relying on human failings.


>> Yes, but that requires programmer discipline, which is unreliable and
>> scales poorly.
> 
> I think it could scale very well with the right language support. But
> that's another subject.

I think this is the subject <g>.


>> Very few programmers are able to successfully write such
>> code. With FP programming, most programmers will be able to.
> 
> That's obviously true. I'm not sure why it's relevant though. You said
> you had a "pure" keyword in mind for that. But if I don't use the pure
> keyword, then thread safety should be down to me, right?

The const will still be needed.


>> It has come up before. The answer is to not declare logically const
>> things as being const, because they aren't const. Logical constness
>> belongs as a comment because it is not compiler checkable.
> But you might want to declare logically const things as being const
> for efficiency reasons.

There aren't any optimizations that logical const enables, for the simple reason that a state change will invalidate the optimization, and you already agreed that logically const objects change state.


> And to argue that "they aren't const" is a
> matter of definition. "const" in C++ is /defined/ to mean logical
> const, so const they are! You use the word to mean "physically const".
> We don't all agree on the definition.

C++ invented the term "logical const" because people clearly were surprised by const references not being constant. But that's not really relevant here, what is relevant is what is the right definition of const for D. I think D should have an intuitive, useful, and checkable notion of const.

C++'s is not intuitive, marginally useful, and not checkable.


> Suppose there exists a class String with member function uppercase(),
> declared const (or invariant, as you would have it in D). So far, so
> good.
> 
> But then, along comes a better (or at least, different) string class
> with the same interface, and it happens to be faster, and I want to
> use the new FastString class in place of the old String class. All I
> have to do is search and replace, right? Or just make an alias. But
> there's a problem. Turns out, the reason it's faster is because it
> caches some results internally so it doesn't have to keep recomputing
> them.
> 
> From the point of view of "implementation should be independent of
> interface" this internal detail is something I shouldn't need to know
> or care about.
> 
> But now suddenly, I do need to know about it. And care. Because now
> that uppercase() function I mentioned earlier is no longer physically
> const. That means, if I use it as a drop-in replacement, my code now
> won't compile.

It's a different interface if one is const and the other mutable. It really IS different (repeating myself!). A user of it would care, because now he'd know that it may no longer be thread-safe.


>> I'll reiterate that const-that-isn't-checkably-constant has no value
>> beyond being a comment, so it might as well just be a comment.
> 
> Oh contraire. It /is/ meaningful. It is a cast iron guarantee that no
> non mutable (which I argue should imply non-private) variables will be
> modified.

No, it isn't, because:

1) The supplier of the type may change its internals to mutable. You recompile, now it crashes in your multithreaded environment.
2) D has opaque types where the contents are not knowable.
3) It becomes impossible to write thread safe generic code.

> Without the const declaration, you do not have that
> guarantee. It's a guarantee *which the compiler can use* to help the
> programmer catch errors! For example, this C++
> 
> class C
> {
>     mutable int x;
>     int y;
> 
>     void f() const;
>     {
>         x = 1;
>         y = 2; /* The compiler catches this error */
>      }
> }
> 
> See - this is useful to me. If I had not declared f const (which is
> basically what you're suggesting), then the compiler would have been
> no help to me whatsoever. It would not have flagged y=2 as an error.
> 
> I /want/ the compiler to assist me by flagging errors like this. I
> want it to be a compile-time error for f to modify y. But as soon as
> remove the words "const" and "mutable" from that example, I lose that
> compiler-assistance.

You can write your class as:

	int x;
	const int y;


>> The D compiler does not necessarily know all there is to know about a type:
> 
> OK. Good point. I hadn't thought that one through.
> 
> The difficulty is, if you don't allow mutable member variables, then
> we're back to what started this thread - transitivity sucks.

Transitivity doesn't have any use for logical const'ness, I'll agree with that.

> See, I was basically arguing with you, that transitivity is a good
> thing -- /provided/ we can have mutable members. Now, if we can't have
> that, then suddenly transitivity no longer seems so welcoming. If
> const-transitivity becomes a strait-jacket, then folk are going to end
> up just not declaring anything const at all, because they can't get
> their code to compile otherwise.

The design patterns const is often used for in C++ won't work in D, that I agree with. But there will be new design patterns available, I argue very valuable ones, that are impossible with C++ const.
September 13, 2007
Walter Bright wrote:

> 1) Programmers aren't that good. Sure, a few are, but most of us aren't, and we write code we imagine is thread safe but really isn't. See Scott Meyer's article on double checked locking, for example.
> 
> 2) Even if a programmer is that good, programmers still have bad days. It's why pilots have checklists for everything, and always use them, no matter how well they've memorized them.
> 
> 3) Programmers being good doesn't help the code auditor, who has to always assume the worst.
> 
> 4) Automation to detect flaws is better than relying on human failings.

You use these arguments to justify that it's the compiler's job to help the user write thread-safe code.  However, the same exact arguments apply to helping the user write "modification-safe" code -- I.e. code that doesn't modify anything not intended.  This is what C++'s const/mutable combo gives you, and it seems useful to me.

> C++'s is not intuitive, marginally useful, and not checkable.

Well, there's no proof at this point that D's const will be any more intuitive than C++'s (so far it's seeming less so).  As for "marginally useful" -- C++ const helps people document the intended logical operation of their code.  It's quite useful in that.  Just not for optimization or multithreading purposes.    As for "not checkable" -- what are all those "const int* doesn't match int*" errors I get when I try to compile my C++ code?  It serves its purpose.

You are fond of comparing C++ const to painting a black stripe on your chest and calling it a seat belt.  It's humorous, but the comparison is inapt.  C++ const is in fact a *LOT* like a seat belt in that it'll save your a** maybe 90% of the time, but not that other 10%.  A painted stripe will save you %0 of the time, and that's simply not true of C++ const.  Used properly, const in C++ probably points out something like 90% of potentially dangerous mistakes.  Note: *Not talking about multithreading mistakes here!*, but rather more mundane mistakes.  But programmers make plenty of the mundane kinds too.

I think a more apt seatbelt analogy would be "seatbelts are worthless because all it takes is a simple pair of scissors to render them ineffective."  That doesn't make seatbelts any less useful for those of use who use them properly.  What you're after with D's const is not a seatbelt but like some sort of full-body airbag that will let you drive into oncomming traffic at 200MPH and still walk away unscathed.  (like this maybe: http://www.zorb.com/ :-))

The goal of making life easy for multithreaded programming is great. But I think the less stringent variant of const found in C++ also has its uses.  Maybe it's impossible to design a usable system that provides both, but I think you do the entire C++ community a disservice by offhandedly condemning C++ const as merely 'marginally useful'.

--bb
September 13, 2007
Bill Baxter Wrote:

> Walter Bright wrote:
> 
> > 1) Programmers aren't that good. Sure, a few are, but most of us aren't, and we write code we imagine is thread safe but really isn't. See Scott Meyer's article on double checked locking, for example.
> > 
> > 2) Even if a programmer is that good, programmers still have bad days. It's why pilots have checklists for everything, and always use them, no matter how well they've memorized them.
> > 
> > 3) Programmers being good doesn't help the code auditor, who has to always assume the worst.
> > 
> > 4) Automation to detect flaws is better than relying on human failings.
> 
> You use these arguments to justify that it's the compiler's job to help the user write thread-safe code.  However, the same exact arguments apply to helping the user write "modification-safe" code -- I.e. code that doesn't modify anything not intended.  This is what C++'s const/mutable combo gives you, and it seems useful to me.
> 
>  > C++'s is not intuitive, marginally useful, and not checkable.
> 
> Well, there's no proof at this point that D's const will be any more intuitive than C++'s (so far it's seeming less so).  As for "marginally useful" -- C++ const helps people document the intended logical operation of their code.  It's quite useful in that.  Just not for optimization or multithreading purposes.    As for "not checkable" -- what are all those "const int* doesn't match int*" errors I get when I try to compile my C++ code?  It serves its purpose.
> 
> You are fond of comparing C++ const to painting a black stripe on your chest and calling it a seat belt.  It's humorous, but the comparison is inapt.  C++ const is in fact a *LOT* like a seat belt in that it'll save your a** maybe 90% of the time, but not that other 10%.  A painted stripe will save you %0 of the time, and that's simply not true of C++ const.  Used properly, const in C++ probably points out something like 90% of potentially dangerous mistakes.  Note: *Not talking about multithreading mistakes here!*, but rather more mundane mistakes.  But programmers make plenty of the mundane kinds too.
> 
> I think a more apt seatbelt analogy would be "seatbelts are worthless because all it takes is a simple pair of scissors to render them ineffective."  That doesn't make seatbelts any less useful for those of use who use them properly.  What you're after with D's const is not a seatbelt but like some sort of full-body airbag that will let you drive into oncomming traffic at 200MPH and still walk away unscathed.  (like this maybe: http://www.zorb.com/ :-))
> 
> The goal of making life easy for multithreaded programming is great. But I think the less stringent variant of const found in C++ also has its uses.  Maybe it's impossible to design a usable system that provides both, but I think you do the entire C++ community a disservice by offhandedly condemning C++ const as merely 'marginally useful'.
> 
> --bb

D's current const fixes both problems rather nicely, I still can't see the big argument against it semantically (syntax is a matter of taste, I like it, but it appears no one else does).
September 13, 2007
Walter Bright wrote:
> Janice Caron wrote:

[snip]

>> And to argue that "they aren't const" is a
>> matter of definition. "const" in C++ is /defined/ to mean logical
>> const, so const they are! You use the word to mean "physically const".
>> We don't all agree on the definition.
> 
> C++ invented the term "logical const" because people clearly were surprised by const references not being constant.

The term may be new from the C++ community, but the notion is much, much older, and it's certainly nothing much to do with "const references not being constant".

In an OO world, objects perform actions in response to
messages that they receive.  Logical constness means
that their responses to those messages are unchanged.
It's not about implementation details such as internal
state.  Logical const is about an interface, and it's
not a C++ idea, though C++ helped to clarify it.

> But that's not really
> relevant here, what is relevant is what is the right definition of const
> for D.

"Those who cannot learn from..."

We can't ignore learning from the history of immutability and of read-only views in other languages if we want to find what's best for D.  (That doesn't, of course, mean that new ideas shouldn't also be explored.)

> I think D should have an intuitive, useful, and checkable notion of const.

These may be forces that are in conflict; coming up with something in the sweet spot is the challenge.  (It may be that in some areas some of these ideas are complementary, which is nice when it happens.)

C++'s const seems intuitive to many of us, but not to others. It's checkable in some sense, but not in another.  It's not much use for optimization (though it is some).  And it's very useful, though Walter disagrees with that assessment.

Logical const reflects something meaningful at an interface level.  Deep physical const isn't so useful there; you might have an const id, identifying a changing external resource: indirection sinks any attempt to make enforceable const reflect actual unchanging behavior, unless you take it much further in the direction of "pure".  Physical const is an implementation detail.  Logical const is an interface issue. Tools can deal more easily with implementation issues than with design issues, but we shouldn't let the tools get in the way of good design.

> C++'s is not intuitive, marginally useful, and not checkable.

Just thought we should flag that last sentence as subjective propaganda in case anyone still thought Walter was bizarrely neutral on the C++/D discussions >;-)

-- James
September 13, 2007
Walter Bright wrote:
> Janice Caron wrote:
>> It's a solution which doesn't scale.
>>
>> And there goes encapsulation...
> 
> At least for this example, the same issue exists when attempting to do "const-correctness" in C++.

No, it doesn't.  C++ doesn't hide the references from you with its syntax, but the const Rectangle having a reference to a Raster would *not* imply constness of that Raster in C++.  (Indeed, this is a fairly common situation, whether Raster is held by reference or some kind of pointer.)

> You cannot paste const on at the top level,
> it has to be put in at every level underneath it.

I'm not sure what you mean by that.  There's not much context in which to interpret it.

-- James
September 13, 2007
Walter Bright wrote:
> Janice Caron wrote:
>> I was trying to simplifly. Sigh. The gist of this particular example is that have what /starts off/ as perfectly good, non-member-modifying code, and all is well. But then you later put in some diagnostics to make it write stuff to a file (coz you're debugging) and suddenly it no longer compiles and you have keep taking const out of your code all over the place until it does.
> 
> Or you can cast away const-ness. But you are on your own if you do that.

Well, in C++ you'd be fine, but in D I thought the behavior
if you modified something after casting away const was
undefined, so you're out of luck.  (Not that you'd need
to cast away const for this in C++.)

-- James
September 13, 2007
James Dennett wrote:
> Walter Bright wrote:
>> You cannot paste const on at the top level,
>> it has to be put in at every level underneath it.
> 
> I'm not sure what you mean by that.  There's not much
> context in which to interpret it.


Try turning a char* into a const char* at the top level of your program where you don't use const anywhere else. You'll have to add it to every function that takes that variable as an argument, then recursively add it to every function that one calls.
September 13, 2007
On 9/13/07, Walter Bright <newshound1@digitalmars.com> wrote:
> See Scott Meyer's article on double checked locking, for example.

Just read it. And fear not - I wouldn't make that mistake.

> > I think it could scale very well with the right language support. But that's another subject.
>
> I think this is the subject <g>.

I could start another thread (...newsgroup thread, I mean...) for that.


> > programmer catch errors! For example, this C++
> >
> > class C
> > {
> >     mutable int x;
> >     int y;
> >
> >     void f() const;
> >     {
> >         x = 1;
> >         y = 2; /* The compiler catches this error */
> >      }
> > }
> >
> > See - this is useful to me. If I had not declared f const (which is basically what you're suggesting), then the compiler would have been no help to me whatsoever. It would not have flagged y=2 as an error.

> You can write your class as:
>
>         int x;
>         const int y;

no I can't, because then I can't add a member function g

class C
{
    mutable int x;
    int y;

    void f() const;
    {
        x = 1;
        y = 2; /* Error - f is const */
     }

    void g()
    {
        y = 3; /* OK -*/
    }
}

Much of what you said hinges on keywords being intuitive, and on that score, I must agree with you. To me, C++'s const /means/ "logical const", but only because I've got used to it.

If we're going to use better, more intuitive keywords, then I would strongly argue in favor of using "readonly" to mean "a read-only view of stuff that might be changed through some other reference", and "const" to mean "stuff that can never be changed, ever".

The distinction between logical const and physical const *disappears completely* if const is not transitive. That is, with non-transitive const, one can always write a class differently such that "mutable" is never required. Essentially, you replace

class C
{
    mutable T x;
}

with

class C
{
    T * x;
}

Then you can let x be constant, but still modify the actual variable itself through *x.

The difficulty for me is that you want to get rid of intransitive const, and /also/ disallow mutable.

I /like/ your syntax for applying constness to everything inside the brackets. The notion that C++'s

int const * const * const * * * p;

can be written instead as:

const(int **)*** p;

definitely has its appeal. If you so chose, you could allow the same functional notation to remove constness, so that

int * * * const * const * const p;

could be written in D as

const(mutable(int **)***) p;

...but if I've followed this argument correcly, you're not likely to do that, because you believe that allowing mutable members to be accessed through a const pointer - however indirectly - screws things up for mulithreading. And of course, you'd be right.

...which leads me to the conclusion that - in partial agreement with you - allowing mutable members to be accessed through a const pointer, even indirectly, should not be allowed ... unless, and this is the rub, *UNLESS IT CAN BE MADE THREADSAFE*

...and I believe it can. As you quite rightly point out, "mutable" isn't enough. Even synchronised isn't enough. But I could suggest a new language feature that /would/ be enough. I'll start a new thread for it, once I've composed my thoughts into something approaching documentation.

So ... you've convinced me. Now I want to convince you. The use cases that have been listed so far justify /trying/. Your desire to make multithreading easier also justifies trying. Hopefully there is a way we can get the best of both worlds.
September 13, 2007
On 9/13/07, Janice Caron <caron800@googlemail.com> wrote:
> with non-transitive
> const, one can always write a class differently such that "mutable" is
> never required. Essentially, you replace
>
> class C
> {
>     mutable T x;
> }
>
> with
>
> class C
> {
>     T * x;
> }
>
> Then you can let x be constant, but still modify the actual variable itself through *x.

Except of course that using the "mutable" keyword ensures that /only/ class member functions can modify x, /and/ gives you value semantics instead of pointer semantics to boot.
September 13, 2007
James Dennett wrote:
> Walter Bright wrote:
>> Or you can cast away const-ness. But you are on your own if you do that.
> 
> Well, in C++ you'd be fine, but in D I thought the behavior
> if you modified something after casting away const was
> undefined, so you're out of luck.  (Not that you'd need
> to cast away const for this in C++.)

It's also undefined behavior if you cast a pointer to an int. You need to know what you're doing.