View mode: basic / threaded / horizontal-split · Log in · Help
September 13, 2007
Re: Transitive const sucks
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
Re: Transitive const sucks
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
Re: Transitive const sucks
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
Re: Transitive const sucks
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
Re: Transitive const sucks
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
Re: Transitive const sucks
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
Re: Transitive const sucks
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
Re: Transitive const sucks
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
Re: Transitive const sucks
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
Re: Transitive const sucks
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.
3 4 5 6 7 8
Top | Discussion index | About this forum | D home