www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Transitive const sucks

reply Alex Burton <alexibu mac.com> writes:
While const is being reexamined....

I create an interface :

interface Server
{
    Data getData() const;
};

I then write half my application using a stub implementation of Server, which
is refered to using const references, because this application doesn't modify
the server (the interface has no non const members).
Then the time comes to implement the Server properly using a socket to
communicate with a server.

interface Socket
{
    void send(const Data d);
    Data receive() const;
};

class ServerImplementation : Server
{
    Socket sock;
    Data getData() const
    {
        sock.send(request);
        return sock.receive();
    }
};

My application doesn't compile because I am refering to using const Server
references every where, and it turns out the server class needs to modify it's
socket to implement the getData method.

This is wrong.
The socket is not part of the ServerImplementation, it's just that D can't tell
the difference between things that are part of a class and things that are not.
I now have to make the getData method non const and all the references to
Server non const, and possibly not use any const references in my application
again, as similar examples can be constructed for all sorts of things, and
basically the const system does not work.

I think it's great to search for a better const system that c++, but if you
have a great system for allowing compiler to optimise, but the programmer can't
actually use the system to describe reality then it's not better at all.

The solution is to be able to say whether the socket is part of the server or
not, then the const system can be made to work, as it did in c++.
I am not saying that c++ const is great but at least I can use it to describe
reality (although it's cumbersome, and there are a lot of reasons why I am
itching to convert all my stuff to D).

Sincerely,
Alex
Sep 11 2007
next sibling parent reply Regan Heath <regan netmail.co.nz> writes:
Alex Burton wrote:
 While const is being reexamined....
 
 I create an interface :
 
 interface Server
 {
     Data getData() const;
 };
 
 I then write half my application using a stub implementation of Server, which
is refered to using const references, because this application doesn't modify
the server (the interface has no non const members).
 Then the time comes to implement the Server properly using a socket to
communicate with a server.
 
 interface Socket
 {
     void send(const Data d);
     Data receive() const;
 };
 
 class ServerImplementation : Server
 {
     Socket sock;
     Data getData() const
     {
         sock.send(request);
         return sock.receive();
     }
 };
 
 My application doesn't compile because I am refering to using const Server
references every where, and it turns out the server class needs to modify it's
socket to implement the getData method.
 
 This is wrong.
 The socket is not part of the ServerImplementation, it's just that D can't
tell the difference between things that are part of a class and things that are
not.
 I now have to make the getData method non const and all the references to
Server non const, and possibly not use any const references in my application
again, as similar examples can be constructed for all sorts of things, and
basically the const system does not work.
 
 I think it's great to search for a better const system that c++, but if you
have a great system for allowing compiler to optimise, but the programmer can't
actually use the system to describe reality then it's not better at all.
 
 The solution is to be able to say whether the socket is part of the server or
not, then the const system can be made to work, as it did in c++.
 I am not saying that c++ const is great but at least I can use it to describe
reality (although it's cumbersome, and there are a lot of reasons why I am
itching to convert all my stuff to D).
 
 Sincerely,
 Alex

I remember your example case from your earlier thread. :) So, correct me if I'm wrong. You want to be able to specify a reference which itself cannot change (perhaps a requirement, perhaps not?), but refers to an object which can. And you want this reference to be usable inside a const method. For the first I would suggest a new syntax like: class Foo { int a; } const(Foo&) pFoo; //pFoo cannot change, pFoo.a can This is analogous to my other idea[1]. For the second, either: 1. the compiler should assume that as the author used const on this reference, and intentionally applied it only to the reference itself, that changes made _via_ this reference are not covered by the const method contract. 2. we need a new keyword to indicate which references/pointers to mutable data can be used in a const method. i.e. "mutable" in either case you would then be able to make changes to the referenced data from within a const method. [1]See my recent posts to the "Const sucks" thread for the idea that: class Foo { int a; } const(Foo*) pFoo; //pFoo can change, pFoo.a cannot would declare a tail const class reference. Regan
Sep 11 2007
next sibling parent Regan Heath <regan netmail.co.nz> writes:
Regan Heath wrote:
 For the first I would suggest a new syntax like:
 
 class Foo { int a; }
 const(Foo&) pFoo;  //pFoo cannot change, pFoo.a can

Actually, correction, this should probably be: const(&Foo) pFoo; //pFoo cannot change, pFoo.a can The position of & in this case is important becase we don't want it confused with reference types but rather we're saying: "the reference/pointer/address of this is const"
Sep 11 2007
prev sibling parent Alex Burton <alexibu mac.com> writes:
Regan Heath Wrote:


 I remember your example case from your earlier thread. :)
 
 So, correct me if I'm wrong.  You want to be able to specify a reference 
 which itself cannot change (perhaps a requirement, perhaps not?), but 
 refers to an object which can.  And you want this reference to be usable 
 inside a const method.
 
 For the first I would suggest a new syntax like:
 
 class Foo { int a; }
 const(Foo&) pFoo;  //pFoo cannot change, pFoo.a can
 
 This is analogous to my other idea[1].
 
 For the second, either:
 
 1. the compiler should assume that as the author used const on this 
 reference, and intentionally applied it only to the reference itself, 
 that changes made _via_ this reference are not covered by the const 
 method contract.
 
 2. we need a new keyword to indicate which references/pointers to 
 mutable data can be used in a const method. i.e. "mutable"
 
 in either case you would then be able to make changes to the referenced 
 data from within a const method.

Yes essentially we need to be able to distinguish between mutable references and non mutable references. The latter being logically part of the class. Note I would not suggest a mutable keyword that could be applied to class members that are structs or ints, like you can use in c++, as this is clearly bad for optimisation and is logically wrong. But what I do want is essentially an ability to mark class references as mutable. On the one hand I totally empathise with being able to write 'Socket sock;' for members that are classes possibly polymorphic. Instead of in c++ aggregate_ptr<Socket> sock, or scoped_ptr<Socket> sock or shared_ptr<Socket> sock; Most of the needs of having these different pointer types was to do with memory management, which has been eliminated in D. But there was also information on aggregation (part of relationships) in there. The mutableness is just a property of the member not being part of the class, so what we really need is something like: class Whole { Part p; not_a_part_keyword NotAPart np; };
Sep 11 2007
prev sibling next sibling parent reply "Janice Caron" <caron800 googlemail.com> writes:
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

Your problem generalises to anything "streamy"

Going way back to the old C way of doing things
 FILE * f;
 fprintf(f, fmt, stuff);

You'd expect that to work, even if you copied from from inside a const
struct, right? But then the type of f would have to change to

 const FILE * f;

which is C-speak for "f is a const pointer to mutable FILE". Now throwing in
transitivity would stop it all working.

Moving forward in time to the modern era of objects, in general, you would
want:

 stream.write(x);

to work even if the variable "stream" was const. (That is, if the
/reference/ was const, not the data which is pointed to by the reference).
The stack variable can be const, but the heap data needs to be mutable.

I don't have a solution, except to agree that "head const" does seem to be
required after all.
Sep 11 2007
parent reply Alex Burton <alexibu mac.com> writes:
Janice Caron Wrote:

 Your problem generalises to anything "streamy"

It then further generalised to anything "state machiney" :) State machines are essential for computers to work. The processor itself is a state machine. The stack has state. If I was to implement a processor emulator in D I would have the same problem. The idea that you can just 'get' a value from memory without modifing anything is an illusion created by the computer. Alex
Sep 11 2007
next sibling parent reply Sean Kelly <sean f4.ca> writes:
Janice Caron wrote:
 On 9/11/07, *Alex Burton* <alexibu mac.com <mailto:alexibu mac.com>> wrote:
 
     The idea that you can just 'get' a value from memory without
     modifing anything is an illusion created by the computer.
 
 
 Yes, it's even more obvious with a read than with a write.
 
 Another interesting use-case involves caching. I'll spare you the boring 
 details, but the essence is, I once had a class member function that did 
 a lookup, much like an associative array, and the prototype would have 
 been something like
 
  int lookup(int key) const;
 
 This was C++, so the const was at the end. The thing is, all the data 
 was in a file, and so the function had to open a file, read it, close 
 the file, and return the data. Well, naturally, it cached some data 
 internally to avoid too many file accesses. And - also naturally - I did 
 not load the entire file into memory at constructor time because huge 
 parts of it might not be needed at all. The file access was deferred 
 until it was needed, and cached appropriately.
 
 So the function was declared const, because /conceptually/ it was.
 
 But the class had a mutable cache, declared with the C++ keyword "mutable"
 
 Transitivity would wreck that.

My classic example for the need of "mutable" is a mutex used to synchronize access to class data. The mutex must be modified even for read operations. But for better or worse, D const has a different aim. The goal is largely to produce a system which allows for compiler optimization rather than to enforce some sort of logical restrictions on behavior. I suspect this means that D apps won't look very much like C++ apps in terms of how const is used, and the overall utility for the average programmer may well be somewhat small. Sean
Sep 11 2007
next sibling parent reply Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
Sean Kelly wrote:
 read operations.  But for better or worse, D const has a different aim. 
  The goal is largely to produce a system which allows for compiler 
 optimization rather than to enforce some sort of logical restrictions on 
 behavior.  I suspect this means that D apps won't look very much like 
 C++ apps in terms of how const is used, and the overall utility for the 
 average programmer may well be somewhat small.
 
 
 Sean

I'm don't think that's entirely true. 'invariant' the keyword is indeed made to allow several compiler and program optimizations, but 'const' the keyword is really for enforcing program contracts and restrictions, thus improving safety. I don't even think 'const' the keyword has any use whatsoever for optimization. -- Bruno Medeiros - MSc in CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Sep 11 2007
parent Sean Kelly <sean f4.ca> writes:
Bruno Medeiros wrote:
 Sean Kelly wrote:
 read operations.  But for better or worse, D const has a different 
 aim.  The goal is largely to produce a system which allows for 
 compiler optimization rather than to enforce some sort of logical 
 restrictions on behavior.  I suspect this means that D apps won't look 
 very much like C++ apps in terms of how const is used, and the overall 
 utility for the average programmer may well be somewhat small.

I'm don't think that's entirely true. 'invariant' the keyword is indeed made to allow several compiler and program optimizations, but 'const' the keyword is really for enforcing program contracts and restrictions, thus improving safety. I don't even think 'const' the keyword has any use whatsoever for optimization.

True enough. But the current design (ie. transitive and lacking 'mutable') is such that 'const' may have limited utility for UDTs where logical state is not equivalent to physical state. I still think this is fine, as I'd prefer something simple and understandable, but I wonder whether this will be sufficient for enterprise programmers looking to switch from C++ to D. Only time will tell, I suppose. Sean
Sep 11 2007
prev sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
Sean Kelly wrote:
 But for better or worse, D const has a different aim. 
  The goal is largely to produce a system which allows for compiler 
 optimization rather than to enforce some sort of logical restrictions on 
 behavior.

This is not the primary goal (it is a side effect of the primary goals). The goals are: 1) Make functional style programming possible, which will become extremely important as people will start using all those lovely cores. 2) Provide compiler enforced semantic guarantees, which improves the specification of interfaces between disparate parts of code. 3) Be able to treat invariant references as value types. This, for example, makes manipulating strings as easy as manipulating ints. 4) Makes COW programming checkable and enforceable. (COW programming is an important part of many styles of programming.) Without transitive const, COW is overly reliant on programmer discipline.
 I suspect this means that D apps won't look very much like 
 C++ apps in terms of how const is used, and the overall utility for the 
 average programmer may well be somewhat small.

Yes, it will be used differently. Whether it is overall better or not only experience will tell.
Sep 12 2007
next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Walter Bright" <newshound1 digitalmars.com> wrote in message 
news:fc837t$29ni$1 digitalmars.com...
 Sean Kelly wrote:
 But for better or worse, D const has a different aim. The goal is largely 
 to produce a system which allows for compiler optimization rather than to 
 enforce some sort of logical restrictions on behavior.

This is not the primary goal (it is a side effect of the primary goals). The goals are: 1) Make functional style programming possible, which will become extremely important as people will start using all those lovely cores.

How does making const transitive allow for functional programming? From what I understand (and that's not much) about functional programming, it's programming without side effects. Consider: int x; class X { const int void f() { x += 5; return x; } } int main() { const X x1 = new X; const X x2 = new X; int y = x1.f() * x2.f(); } Although X.f() is const, it has modified global data, so X.f() has side effects. Yet both x1 and x2 are transitive-const, are they not? Therefore, the compiler STILL cannot make any optimizations based on the fact that x1 and x2 are const. I think in order to allow functional programming, you need to introduce a new type of const, for example fconst. If a function is declared fconst, it is not allowed to change any data that is not local to the function, or call a non-fconst function, or to read non-fconst data. If a piece of data is declared fconst, it cannot be changed. I don't think you can make this restriction with const in general as I think it will severely limit current programming styles (not everyone wants to use functional programming, or else we'd all be using scheme).
 2) Provide compiler enforced semantic guarantees, which improves the 
 specification of interfaces between disparate parts of code.

As I showed above, this is not possible with simply transitive const.
 3) Be able to treat invariant references as value types. This, for 
 example, makes manipulating strings as easy as manipulating ints.

I don't understand this, perhaps someone can explain it further. An example of how this helps would be good.
 4) Makes COW programming checkable and enforceable. (COW programming is an 
 important part of many styles of programming.) Without transitive const, 
 COW is overly reliant on programmer discipline.

All I can find on the net about COW programming is the COW programming language. I'm sure this isn't what you meant :) can you please explain this further? -Steve
Sep 12 2007
next sibling parent reply Gregor Richards <Richards codu.org> writes:
Steven Schveighoffer wrote:
 class X
 {
   const int void f()
   {
     x += 5;
     return x;
   }
 }

"const int void f()" is clearly a bad declaration. Anyway, "const int f()" is a function returning a const int, not a const function returning an int, so you wouldn't be able to call it through a const reference. - Gregor Richards
Sep 12 2007
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Gregor Richards" wrote
 Steven Schveighoffer wrote:
 class X
 {
   const int void f()
   {
     x += 5;
     return x;
   }
 }

"const int void f()" is clearly a bad declaration. Anyway, "const int f()" is a function returning a const int, not a const function returning an int, so you wouldn't be able to call it through a const reference. - Gregor Richards

Um... yeah, I meant to say f() is const. Sorry my example code didn't compile for you :P -Steve
Sep 12 2007
prev sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
Steven Schveighoffer wrote:
 "Walter Bright" <newshound1 digitalmars.com> wrote in message 
 1) Make functional style programming possible, which will become extremely 
 important as people will start using all those lovely cores.

How does making const transitive allow for functional programming? From what I understand (and that's not much) about functional programming, it's programming without side effects. Consider:

Although const is necessary for f.p., it is not sufficient for f.p., as your example illustrates.
 I think in order to allow functional programming, you need to introduce a 
 new type of const, for example fconst.  If a function is declared fconst, it 
 is not allowed to change any data that is not local to the function, or call 
 a non-fconst function, or to read non-fconst data.  If a piece of data is 
 declared fconst, it cannot be changed.

The thought is to declare a function as 'pure'.
 3) Be able to treat invariant references as value types. This, for 
 example, makes manipulating strings as easy as manipulating ints.

I don't understand this, perhaps someone can explain it further. An example of how this helps would be good.

Strings in, say, Perl or Basic "just work". Just like integers "just work" in C. Nobody ever gives a thought to them inadvertently changing value. But strings in C, everybody worries about them changing value, and such is a major source of bugs.
 4) Makes COW programming checkable and enforceable. (COW programming is an 
 important part of many styles of programming.) Without transitive const, 
 COW is overly reliant on programmer discipline.

All I can find on the net about COW programming is the COW programming language. I'm sure this isn't what you meant :) can you please explain this further?

copy-on-write
Sep 12 2007
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Walter Bright" wrote
 Steven Schveighoffer wrote:

 Although const is necessary for f.p., it is not sufficient for f.p., as 
 your example illustrates.

 The thought is to declare a function as 'pure'.

OK, but why not allow BOTH logical const and transitive const? What if const exists as it does, except the mutable keyword overrides it? As long as pure functions can only call other pure functions and use const basic types, then isn't f.p. still possible? You could even use const types that have mutable parts as long as you are only calling pure functions on those types (as those wouldn't be able to access the mutable piece).
 3) Be able to treat invariant references as value types. This, for 
 example, makes manipulating strings as easy as manipulating ints.

I don't understand this, perhaps someone can explain it further. An example of how this helps would be good.

Strings in, say, Perl or Basic "just work". Just like integers "just work" in C. Nobody ever gives a thought to them inadvertently changing value. But strings in C, everybody worries about them changing value, and such is a major source of bugs.

I understand why it is good to have strings immutable, as I've used Java quite a bit. But how does transitive const guarantee that a string's value won't change? Don't you just need immutable strings? What I was looking for is: example = ??? without transitive const: life sucks in this example because you can't do ... with transitive const: life is better because now I can do ...
 4) Makes COW programming checkable and enforceable. (COW programming is 
 an important part of many styles of programming.) Without transitive 
 const, COW is overly reliant on programmer discipline.

All I can find on the net about COW programming is the COW programming language. I'm sure this isn't what you meant :) can you please explain this further?

copy-on-write

ok, so you are saying that with transitive const, this is enforceable, by the compiler I'm assuming. Give me an example of how having transitive const makes COW enforceable by the compiler and doesn't rely on the programmer. I understand much better with examples rather than abstract arguments if you notice :) -Steve
Sep 12 2007
prev sibling parent reply Sean Kelly <sean f4.ca> writes:
Walter Bright wrote:
 Sean Kelly wrote:
 I suspect this means that D apps won't look very much like C++ apps in 
 terms of how const is used, and the overall utility for the average 
 programmer may well be somewhat small.

Yes, it will be used differently. Whether it is overall better or not only experience will tell.

This was perhaps too strong a statement on my part. Given the design of const, I'm beginning to suspect that while the syntax of D is fairly similar to C++, I think the structure of a D app five years from now may well look very little like that of a C++ app. The recent D features all lend themselves to a more functional style of programming. This is probably a good thing in terms of scaling towards the future, but it could be surprising for someone who thinks D is C merged with Java :-) Sean
Sep 12 2007
parent Walter Bright <newshound1 digitalmars.com> writes:
Sean Kelly wrote:
 This was perhaps too strong a statement on my part.  Given the design of 
 const, I'm beginning to suspect that while the syntax of D is fairly 
 similar to C++, I think the structure of a D app five years from now may 
 well look very little like that of a C++ app.  The recent D features all 
 lend themselves to a more functional style of programming.  This is 
 probably a good thing in terms of scaling towards the future, but it 
 could be surprising for someone who thinks D is C merged with Java :-)

Interestingly, the look of C++ apps have changed *substantially* over time as new philosophies of how to use it have evolved. One could note that I'm a bit stuck in the past, as my C++ code (the D front end) is definitely an old style of C++. It has very little in common with the modern style (see Boost for that).
Sep 12 2007
prev sibling next sibling parent reply Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
Janice Caron wrote:
 On 9/11/07, Steven Schveighoffer <schveiguy yahoo.com> wrote:
 What is the argument against mutable again?

In C++, you can do this: class C { public: mutable int x; C() { x = 0; } void f() const { x = 5; } } main() { const C c; printf("%d\n", c.x); /* prints 0; */ c.f(); printf("%d\n", c.x); /* prints 5; */ } This is clearly nonsense. That would be disallowed in my scheme. (...at least, if main() was in a different file from the definition of C).

And why is this nonsense?? Because x is public? I don't see the problem here. -- Bruno Medeiros - MSc in CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Sep 11 2007
parent Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
Janice Caron wrote:
 And why is this nonsense?? Because x is public? I don't see the problem
 here.

What's the use-case for mutable non-private members? I would say that it's nonsense if there's no need for them. "Nonsense" may be the wrong word. "Should not be part of D" is more what I really mean.

I can't name a use case right now, but I do think significant use-cases for mutable non-private members exist. -- Bruno Medeiros - MSc in CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Sep 12 2007
prev sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
Janice Caron wrote:
 So the function was declared const, because /conceptually/ it was.
 
 But the class had a mutable cache, declared with the C++ keyword "mutable"
 
 Transitivity would wreck that.

You're right in that transitive const does not support the notion of "logical const" (which is the usual term for what you are referring to). The problem with logical const, however, is it offers no semantic guarantees. I pass a logical const reference around, and the underlying data may or may not change. I have no guarantees one way or the other, and even worse, I can't even tell this is happening. "I" here meaning the compiler, and our hapless code auditor. This just pulls the rug out from under: 1) functional programming 2) multithreaded programming 3) having a tightly specified interface It goes back to painting a stripe across your hips and calling it a seatbelt. Given this, it isn't any surprise that C++ is disastrously difficult to write functional & multithreaded programs in, and C++ mutability is one of the reasons why. In C++, you can write const this and const that and it doesn't mean jack squat. Many experienced C++ programmers will tell you that const is little more than a documentation aid. In other words, you're quite right that transitive const totally wrecks using const to specify logical constness. And that's a good thing <g>.
Sep 11 2007
next sibling parent reply Reiner Pope <some address.com> writes:
Walter Bright wrote:
 Janice Caron wrote:
 So the function was declared const, because /conceptually/ it was.

 But the class had a mutable cache, declared with the C++ keyword 
 "mutable"

 Transitivity would wreck that.

You're right in that transitive const does not support the notion of "logical const" (which is the usual term for what you are referring to). The problem with logical const, however, is it offers no semantic guarantees. I pass a logical const reference around, and the underlying data may or may not change. I have no guarantees one way or the other, and even worse, I can't even tell this is happening. "I" here meaning the compiler, and our hapless code auditor. This just pulls the rug out from under: 1) functional programming 2) multithreaded programming 3) having a tightly specified interface It goes back to painting a stripe across your hips and calling it a seatbelt. Given this, it isn't any surprise that C++ is disastrously difficult to write functional & multithreaded programs in, and C++ mutability is one of the reasons why. In C++, you can write const this and const that and it doesn't mean jack squat. Many experienced C++ programmers will tell you that const is little more than a documentation aid. In other words, you're quite right that transitive const totally wrecks using const to specify logical constness. And that's a good thing <g>.

Is logical const really that bad? What would happen if you required in the spec that any changes made to a logical const variable must be undoable without changing the semantics of the program? Suppose we have a logical const object with internal state, A. We then call a logical const method, getResult(), which caches the result internally, making the object's internal state B. As long as the caching is done correctly, then A.getResult() == B.getResult(), so the compiler is free to substitute B for A, or A for B whenever it wants. As far as I can see, this re-enables the benefits of transitive const for functional and multi-threaded programming, as the compiler is free to ignore any changes to the variable, just as it is for transitive const. -- Reiner
Sep 12 2007
next sibling parent Bruce Adams <tortoise_74 ya.thewho.co.uk> writes:
Janice Caron Wrote:

 Even something as simple as this needs logical const
 
 class MyMathClass
 {
     invariant int multiply(int x, int y) /* logically const */
     {
         debug logfile.writeLine("multiply.called");
         return x * y;
     }
 
     debug private Stream logfile;
 }
 
 You can't tell me that's not a real world need.
 
 Even something as simple as this needs logical const<br><br>class
MyMathClass<br>{<br>&nbsp;&nbsp;&nbsp; invariant int multiply(int x, int y) /*
logically const */<br>&nbsp;&nbsp;&nbsp;
{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; debug
logfile.writeLine(&quot;multiply.called&quot;);
 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return x *
y;<br>&nbsp;&nbsp;&nbsp; }<br><br>&nbsp;&nbsp;&nbsp; debug private Stream
logfile;<br>}<br><br>You can't tell me that's not a real world need.<br><br>
 

Thanks.
Sep 12 2007
prev sibling next sibling parent Walter Bright <newshound1 digitalmars.com> writes:
Reiner Pope wrote:
 As far as I can see, this re-enables the benefits of transitive const 
 for functional and multi-threaded programming, as the compiler is free 
 to ignore any changes to the variable, just as it is for transitive const.

It doesn't re-enable any benefits, as all the usual synchronization, race conditions, etc., are there in full force. You're back to relying on programmer discipline to make it work right, and experience shows that that doesn't work for multithreading.
Sep 12 2007
prev sibling next sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
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++. You cannot paste const on at the top level, it has to be put in at every level underneath it.
Sep 12 2007
parent reply James Dennett <jdennett acm.org> writes:
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
Sep 12 2007
next sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
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.
Sep 13 2007
parent James Dennett <jdennett acm.org> writes:
Walter Bright wrote:
 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.

OK, now I understand: and what you say is true, const-correctness can't trivially be layered on top of const-ignorant ccode unless you tolerate a boundary layer with const_cast where needed. That's a good thing and a bad thing -- good in that it puts pressure on the lower layers to "do the right thing", bad in that it makes it harder for higher layers to do so if the layers beneath do not. However: for most C++, 99% of the time, it's not a problem. -- James
Sep 13 2007
prev sibling parent reply Bruce Adams <tortoise_74 yeah.who.co.uk> writes:
James Dennett Wrote:

 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.)

[snip]
 
 -- James

This is a good point to emphasise. In object oriented programming there is a clear difference between encapsulating something in an aggregate and merely being associated with it. In the rectangle example a raster is clearly not "part of" a rectangle but it is associated. An important concept here is ownership. In C++ I general find myself labelling pointers as owned and not-owned. If you own a pointer you are responsible for deleting the object it points to. Some experimental static analysis tools are able to use pointers labelled this way to reason about memory allocation. Research suggests (I will have to google around a bit before I can quote references) that adding something to a language to indicate ownership is warranted. D with its garbage collection doesn't have this problem on the surface but there is still a distinction between something that is owned and something that is referred to. This is a distinction that is (as far as I can tell) currently impossible to represent except via comments. In D everything is a reference. Perhaps the solution is to make non-owned members (in the sense of association) pointers and reserve references (minus the pointer) for aggregation. A value keyword might help some but I think it would address the wrong problem. For members of an aggregate const should be transitive. If I cannot change this object then I should not be able to change any of its parts. If there is a logger, cache or raster - Associated - with the class I may still be able to change it - depending on whether that particular associated is marked as const. Regards, Bruce.
Sep 13 2007
parent Sean Kelly <sean f4.ca> writes:
Bruce Adams wrote:
 James Dennett Wrote:
 
 Walter Bright wrote:
 Janice Caron wrote:
 It's a solution which doesn't scale.

 And there goes encapsulation...

"const-correctness" in C++.

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.)

This is a good point to emphasise. In object oriented programming there is a clear difference between encapsulating something in an aggregate and merely being associated with it. In the rectangle example a raster is clearly not "part of" a rectangle but it is associated. An important concept here is ownership. In C++ I general find myself labelling pointers as owned and not-owned. If you own a pointer you are responsible for deleting the object it points to. Some experimental static analysis tools are able to use pointers labelled this way to reason about memory allocation. Research suggests (I will have to google around a bit before I can quote references) that adding something to a language to indicate ownership is warranted.

 D with its garbage collection doesn't have this problem on the surface but
there is still a distinction between something that is owned and something that
is referred to. This is a distinction that is (as far as I can tell) currently
impossible to represent except via comments. In D everything is a reference.
Perhaps the solution is to make non-owned members (in the sense of association)
pointers and reserve references (minus the pointer) for aggregation. A value
keyword might help some but I think it would address the wrong problem.
 
  For members of an aggregate const should be transitive. If I cannot change
this object then I should not be able to change any of its parts.
 If there is a logger, cache or raster - Associated - with the class I may
still be able to change it - depending on whether that particular associated is
marked as const.

For what it's worth, I think object ownership could be communicated via the 'scope' keyword. Walter mentioned in the past that he'd considered allowing it in class scope anyway. Sean
Sep 13 2007
prev sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
Janice Caron wrote:
 Even something as simple as this needs logical const
 
 class MyMathClass
 {
     invariant int multiply(int x, int y) /* logically const */
     {
         debug logfile.writeLine("multiply.called");
         return x * y;
     }
 
     debug private Stream logfile;
 }
 
 You can't tell me that's not a real world need.

You can: 1. make logfile a static member. 2. not use invariant on the multiply. After all, if it isn't actually invariant, the invariant declaration wouldn't have any more meaning than the comment you put on it.
Sep 12 2007
parent reply Walter Bright <newshound1 digitalmars.com> writes:
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.
Sep 12 2007
parent reply James Dennett <jdennett acm.org> writes:
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
Sep 12 2007
parent reply Walter Bright <newshound1 digitalmars.com> writes:
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.
Sep 13 2007
parent James Dennett <jdennett acm.org> writes:
Walter Bright wrote:
 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.

Right. But you've snipped too much context. The point was that C++ allows something, and D doesn't. In D you can't even cast away the const and then modify the underlying object, because it's undefined (so if you do so, you *don't* know what you're doing). The need is to modify state in an object associated with another object. That doesn't change the object in question (either physically or logically) but is disallowed by D, as there's no recognition that association does not imply aggregation/ containment. -- James
Sep 13 2007
prev sibling next sibling parent reply Bruce Adams <tortoise_74 ya.thewho.co.uk> writes:
Janice Caron Wrote:

[snip] 
 This just pulls the rug out from under:
 1) functional programming

Functional programming isn't possible anyway unless the function can also guarantee that it won't modify /global/ variables, and I see no way of specifying that in the prototype. 2) multithreaded programming Likewise, a function cannot be guaranteed to be completely threadsafe unless it guarantees not to modify global variables, and I see no way of specifying that in the prototype.

http://s3.amazonaws.com/dconf2007/WalterAndrei.pdf Regards, Bruce.
Sep 12 2007
parent Walter Bright <newshound1 digitalmars.com> writes:
Janice Caron wrote:
 But then, I don't understand Walter's objections to "logical
 constness".
 
 Seems pretty simple to me. If a function is logically const but not 
 truly const, then don't declare it pure.
 
 Conversely, if it's not declared pure, then it can have logical
 constness.
 
 Isn't that problem solved?

No. Pass a reference to a const through a non-pure function to a pure one.
Sep 12 2007
prev sibling next sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
BTW, Janice, I appreciate your thoughts on this. You're obviously an 
experienced programmer.

Janice Caron wrote:
 But you could guarantee that /non-private/ data will not change. And 
 since non-private data is never seen outside the file in which it is 
 declared, I don't understand why this is a problem.

Because multithreaded programming doesn't start working if the interface hides the changing variables. If it did, parallel programming would be easy. Logical constness is *still* changing state, and since it is, all the boogeymen of synchronization, race conditions, deadlocks, etc., are all there. Logical constness hides the state change from the awareness of the programmer, but it's still changing state.
     and even worse, I can't even tell this is happening. "I" here meaning
     the compiler, and our hapless code auditor.
 Providing you restrict "logical constness" to private variables, is that 
 really true? Inside the module, the compiler knows everything. Outside 
 the module, the private variables are irrelevant anyway as they can 
 never be accessed in any way.

Being in a module doesn't prevent another thread from using the module's interface and thereby changing the state.
 Functional programming isn't possible anyway unless the function can 
 also guarantee that it won't modify /global/ variables, and I see no way 
 of specifying that in the prototype.

The upcoming 'pure' storage class for a function will resolve that.
 Even if you could, it's more complicated than that. A function might 
 modify global variables and yet still be threadsafe - providing it uses 
 mutexes to ensure it has exclusive access to the shared variables. A 
 good example of this is writef(). The writef() function writes to 
 standard output, and so /must/ modify some global state - but it's 
 thread-safe because file access is all mutex locked.

Yes, but that requires programmer discipline, which is unreliable and scales poorly. Very few programmers are able to successfully write such code. With FP programming, most programmers will be able to.
 Moreover, multithreaded programming might /require/ you to lock a mutex, 
 do something, then unlock said mutex. The mutex itself needs to be 
 modifiable - that is, "logically const".
 
 More thought needs to be put into that one.

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.
     3) having a tightly specified interface
 
 Interfaces are my concern. If an Interface specifies that a function be 
 const (in the sense of non modifying member variables), then it would 
 make a lot of sense that classes which implement that interface be 
 allowed to assume the interface means "logical constness", for the 
 reasons of all the use-cases given so far.

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.
     It goes back to painting a stripe across your hips and calling it a
     seatbelt.
 
     Given this, it isn't any surprise that C++ is disastrously difficult to
     write functional & multithreaded programs in, and C++ mutability is one
     of the reasons why. In C++, you can write const this and const that and
     it doesn't mean jack squat. Many experienced C++ programmers will tell
     you that const is little more than a documentation aid.
 
 
 I /am/ an experienced C++ programmer,

I can tell <g>. The logical constness pattern comes from C++.
 Outside the module, that's all you need to 
 know. Inside the module, the compiler knows all anyway.

The D compiler does not necessarily know all there is to know about a type: 1) If you're writing generic code, with things like const(T), you don't know what T is. Therefore, as a programmer, you will not be able to write generically pure code. 2) D allows for so-called abstract types, where the compiler does not know the internals of the type. This is used in the Phobos to hide implementation details. Therefore, the compiler cannot tell if a const T has mutability or not.
 And I'd end up with something that was much nastier than the very thing 
 you are trying to avoid.

The error I see in the example is declaring rand() to be const. Why do that if it IS NOT constant? You can already hide members the user shouldn't be modifying with 'private', why layer 'const' over that, too?
Sep 12 2007
next sibling parent Sean Kelly <sean f4.ca> writes:
Janice Caron wrote:
 On 9/12/07, Walter Bright <newshound1 digitalmars.com> wrote:
 
 Because multithreaded programming doesn't start working if the interface
 hides the changing variables. If it did, parallel programming would be
 easy. Logical constness is *still* changing state, and since it is, all
 the boogeymen of synchronization, race conditions, deadlocks, etc., are
 all there.

I've written a lot of multithreaded programming. A lot. ...which is perhaps a bad thing, because sometimes I forget that D does things differently. I don't need to use a separate mutex class if you've got synchronized.

In general that's true, though you might want a separate mutex class for specialized needs: inter-process synchronization, etc.
 (By the way - could you also allow the spelling "synchronised" for the
 benefit of speakers of British English?) :-)
 
 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?

Yes and no :-) The default implementation isn't that smart, but it can be extended. Tango has a ReadWriteMutex that works like so: auto readWriteLock = new ReadWriteMutex; synchronized( readWriteLock.reader ) { // any number of threads can be here simultaneously } synchronized( readWriteLock.writer ) { // only one thread may enter this block simultaneously // and no threads may be in the reader block above either }
 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.
 
 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.

I've been wondering about this. So far, I've only been able to come up with two options: 1) Cast away const when dealing with 'mutable' data. 2) Store mutable data externally, perhaps in an AA. I haven't yet decided whether there are any fundamental problems with option 1 other than it being generally bad practice. Option 2 seems impractical in most cases however, because any common store of such mutable data must itself be protected by a mutex.
 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?

I think Walter is suggesting that the language should still lend itself to code that isn't error-prone. What remains unclear to me is whether the lack of 'mutable' will actually be an issue or if workarounds and alternate programming techniques (encouraged by the FP model) will be sufficient as a replacement.
 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. 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. 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.

That's a good example. I do wonder, however, whether such an object would be a viable drop-in replacement in all cases. In C++ my answer would be a confident "yes," but in D...
 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. 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!

Agreed. Though I do think it's worth experimenting a bit more with the D approach to see if it is a reasonable replacement. I do think that the traditional OO design that logical const supports in C++ may end up not being the general approach to program design in D. But by the same token, perhaps that isn't sufficient reason to not support the approach. Sean
Sep 12 2007
prev sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
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.


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.

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.
Sep 12 2007
next sibling parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
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
Sep 12 2007
parent Robert Fraser <fraserofthenight gmail.com> writes:
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).
Sep 12 2007
prev sibling next sibling parent James Dennett <jdennett acm.org> writes:
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
Sep 12 2007
prev sibling parent reply Christopher Wright <dhasenan gmail.com> writes:
Walter Bright wrote: well, quite a lot, really.


It seems that you're concentrating heavily on const for objects being 
able to ensure thread safety and do a number of other optimizations. 
Basically, as far as the owner of a const reference is concerned, the 
object and all its methods are 'pure'.

So, since, for all functions that can be optimized similarly and whose 
thread safety can be ensured similarly, we will be adding the pure 
keyword, why not use the pure keyword here?

Then a const reference to an object could be a weaker contract that 
allows everything that people have been wishing the new new const would 
allow, if we can find a solution for it that enough people find useable. 
(And by 'we' I mean 'you'.)
Sep 13 2007
parent Sean Kelly <sean f4.ca> writes:
Christopher Wright wrote:
 Walter Bright wrote: well, quite a lot, really.
 
 
 It seems that you're concentrating heavily on const for objects being 
 able to ensure thread safety and do a number of other optimizations. 
 Basically, as far as the owner of a const reference is concerned, the 
 object and all its methods are 'pure'.
 
 So, since, for all functions that can be optimized similarly and whose 
 thread safety can be ensured similarly, we will be adding the pure 
 keyword, why not use the pure keyword here?

Now there's an idea. I like it. Sean
Sep 13 2007
prev sibling parent Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
Walter Bright wrote:
 Janice Caron wrote:
 So the function was declared const, because /conceptually/ it was.

 But the class had a mutable cache, declared with the C++ keyword 
 "mutable"

 Transitivity would wreck that.

You're right in that transitive const does not support the notion of "logical const" (which is the usual term for what you are referring to). The problem with logical const, however, is it offers no semantic guarantees. I pass a logical const reference around, and the underlying data may or may not change. I have no guarantees one way or the other, and even worse, I can't even tell this is happening. "I" here meaning the compiler, and our hapless code auditor. This just pulls the rug out from under: 1) functional programming 2) multithreaded programming 3) having a tightly specified interface It goes back to painting a stripe across your hips and calling it a seatbelt. Given this, it isn't any surprise that C++ is disastrously difficult to write functional & multithreaded programs in, and C++ mutability is one of the reasons why. In C++, you can write const this and const that and it doesn't mean jack squat. Many experienced C++ programmers will tell you that const is little more than a documentation aid. In other words, you're quite right that transitive const totally wrecks using const to specify logical constness. And that's a good thing <g>.

In the interest of this discussion, here's a very nice article regarding the topic of logical constness: http://www.ddj.com/cpp/184403892 (the fact that it mentions Walter is coincidence, the article is simply the first result I got when I googled for "logical constness") Anyways, we now see that 'mutable' and logical constness is dangerous when one wants to do multi-threaded optimizations. However, since D is going to have several flavors of const (such as 'const', 'invariant' and also the 'pure' functions), why not allowing logical constness on some of these flavors, but disallow it in others. Namely, we could allow logical constness in 'const' and 'invariant' data, that is, changing 'mutable' members of 'const' and 'invariant' data. Thus D would allow for the the use cases of logical constness. But on 'pure' functions, no changes whatsover would be allowed, even of 'mutable' members, and so we could still be able to use the 'pure' construct to safely create multi-threaded an parallelization optimizations. Would this be ok? -- Bruno Medeiros - MSc in CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Sep 12 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

On 9/11/07, Alex Burton <alexibu mac.com> wrote:
 The idea that you can just 'get' a value from memory without modifing
 anything is an illusion created by the computer.

Yes, it's even more obvious with a read than with a write. Another interesting use-case involves caching. I'll spare you the boring details, but the essence is, I once had a class member function that did a lookup, much like an associative array, and the prototype would have been something like int lookup(int key) const; This was C++, so the const was at the end. The thing is, all the data was in a file, and so the function had to open a file, read it, close the file, and return the data. Well, naturally, it cached some data internally to avoid too many file accesses. And - also naturally - I did not load the entire file into memory at constructor time because huge parts of it might not be needed at all. The file access was deferred until it was needed, and cached appropriately. So the function was declared const, because /conceptually/ it was. But the class had a mutable cache, declared with the C++ keyword "mutable" Transitivity would wreck that.
Sep 11 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

On 9/11/07, Janice Caron <caron800 googlemail.com> wrote:
 The thing is, all the data was in a file, and so the function had to open
 a file, read it, close the file, and return the data. Well, naturally, it
 cached some data internally

These days, of course, the data source might not even be a file - it might be an resource you have to fetch from a remote site on the internet using HTTP. Caching is, occasionally, really necessary.
Sep 11 2007
prev sibling next sibling parent reply "Janice Caron" <caron800 googlemail.com> writes:
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

What all of these use-cases have in common is the fact that the state is
private

Suppose that all class and struct members which were declared private, were
always mutable, even if the class instance is const.

That would mean you could do this:

 class RandomNumberGenerator;
 {
     private long seed;

     const int rand() /* guarantees not to modify any non-private member
variables */
     {
         seed = f(seed);
         return (seed >> 32);
     }
 }

 const RandomNumberGenerator rng;
 writefln(rng.rand());

In C++, we would have declared seed "private mutable" to achieve the same
thing, but in D we tend to assume that everything in the same module is
"friendly", so why not just let "private" mean "private mutable"?
Sep 11 2007
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Janice Caron" wrote
 What all of these use-cases have in common is the fact that the state is 
 private

 Suppose that all class and struct members which were declared private, 
 were always mutable,
 even if the class instance is const.

I sort of agree with you, but what about instances where you want derived classes to be able to access the cache? I think having a keyword for mutable is necessary, but what may make sense is to enforce that only non-public members are allowed to be mutable. What is the argument against mutable again? -Steve
Sep 11 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

On 9/11/07, Sean Kelly <sean f4.ca> wrote:
 My classic example for the need of "mutable" is a mutex used to
 synchronize access to class data.  The mutex must be modified even for
 read operations.

Again, if we let D's "private" attribute also imply C++'s "mutable", then that problem goes away.
Sep 11 2007
prev sibling next sibling parent reply "Janice Caron" <caron800 googlemail.com> writes:
On 9/11/07, Steven Schveighoffer <schveiguy yahoo.com> wrote:
 "Janice Caron" wrote
 What all of these use-cases have in common is the fact that the state is
 private

 Suppose that all class and struct members which were declared private,
 were always mutable,
 even if the class instance is const.

I sort of agree with you,

I believe I can make the case for my argument stronger. In general, when I expose a class's interface, the understood contract is: class C { public: /* stuff I want you to know about */ private: /* None of your gorram business. It's PRIVATE. You shouldn't care know or care what's in here */ } To a caller of the code, it should not matter what member functions of C do to its private members, so long as the public interface does what you expect. Here's another use case. Suppose I start off with a simple class like: class C { void f() { /* do something */ } } const C c; c.f(); But later, as I'm debugging the code, I realise it's not doing quite what I want it to do, and I need to put some diagnostic code in to help me with debugging. So I change it to: class C { void f() { debug ++count; /* do something */ } debug private int count; } const C c; c.f(); Whoops! Now it won't compile in debug mode (but it will still compile in release mode). Again, it's because /private/ state is conflicting with the constness.
 but what about instances where you want derived
 classes to be able to access the cache?

I can't think of a use-case for that. Would it not suffice for the base-class to expose protected functions to provide read-only access to the cache?
 I think having a keyword for mutable is necessary

I believe this thread has shown that there is a need for the /functionality/ that the keyword provides, but I'm not completely convinced that we need the keyword itself. If we allow private data to be implicitly mutable then we don't need the keyword.
 but what may make sense
 is to enforce that only non-public members are allowed to be mutable.

My idea would make that enforcement unnecessary
 What is the argument against mutable again?

In C++, you can do this: class C { public: mutable int x; C() { x = 0; } void f() const { x = 5; } } main() { const C c; printf("%d\n", c.x); /* prints 0; */ c.f(); printf("%d\n", c.x); /* prints 5; */ } This is clearly nonsense. That would be disallowed in my scheme. (...at least, if main() was in a different file from the definition of C).
Sep 11 2007
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Janice Caron" wrote
 On 9/11/07, Steven Schveighoffer <schveiguy yahoo.com> wrote:
 "Janice Caron" wrote
 What all of these use-cases have in common is the fact that the state 
 is
 private

 Suppose that all class and struct members which were declared private,
 were always mutable,
 even if the class instance is const.

I sort of agree with you,

I believe I can make the case for my argument stronger. In general, when I expose a class's interface, the understood contract is: class C { public: /* stuff I want you to know about */ private: /* None of your gorram business. It's PRIVATE. You shouldn't care know or care what's in here */ }

Yeah, I understand your original point. I'm not disputing that, which is why I "sort of" agree with you. My point that protected should be able to be mutable as well as private was the point I was trying to make.
 but what about instances where you want derived
 classes to be able to access the cache?

I can't think of a use-case for that.

The one that comes to mind is if you wanted to slightly modify the way a cache was used. The two options are: make the cache protected, and allow derived classes to use it (in which case mutable is required), or re-implement the cache in the slightly modified way in the derived class, in which case you are carrying around 2 caches for no reason.
 Would it not suffice for the base-class to expose protected functions
 to provide read-only access to the cache?

Well, yeah, you could do it that way, but you would have to know before-hand how your cache was going to be accessed/used by the derived classes, therefore restricting the creativity of the author of the derived class.
 What is the argument against mutable again?

In C++, you can do this: class C { public: mutable int x; C() { x = 0; } void f() const { x = 5; } } main() { const C c; printf("%d\n", c.x); /* prints 0; */ c.f(); printf("%d\n", c.x); /* prints 5; */ } This is clearly nonsense.

class NonsenseInD { private int _x = 0; int x() const { return _x;} void f() const { x = 5;} } // could be in another file int main(char[][] args) { const NonsenseInD c = new NonsenseInD; Stdout(c.x).newline; // prints 0; c.f(); Stdout(c.x).newline; // prints 5; } Even with this example, I still don't see why mutable is such a horrible thing... To me it makes sense to have pieces of a class that can change, even when the class is const. You have given several good examples (files, net connections, etc), but I think your idea of enforcing public data being const is probably not feasible. -Steve
Sep 11 2007
parent Christopher Wright <dhasenan gmail.com> writes:
Steven Schveighoffer wrote:
 "Janice Caron" wrote
 On 9/11/07, Steven Schveighoffer <schveiguy yahoo.com> wrote:
 but what about instances where you want derived
 classes to be able to access the cache?


The one that comes to mind is if you wanted to slightly modify the way a cache was used. The two options are: make the cache protected, and allow derived classes to use it (in which case mutable is required), or re-implement the cache in the slightly modified way in the derived class, in which case you are carrying around 2 caches for no reason.

Provide a property to access the cache. It'll likely be inlined, so no performance hit.
Sep 11 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On 9/11/07, Steven Schveighoffer <schveiguy yahoo.com> wrote:

 // tango style
 class NonsenseInD
 {
   private int _x = 0;
   int x() const { return _x;}
   void f() const { x = 5;}
 }

 // could be in another file
 int main(char[][] args)
 {
   const NonsenseInD c = new NonsenseInD;
   Stdout(c.x).newline; // prints 0;

No it won't, because it won't compile, because x is private. (It will compile if main is in the same file as class NonsenseInD, but I can live with that).
Sep 11 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
 No it won't, because it won't compile, because x is private.

Sorry - accept my apologies - I misread your code.
Sep 11 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
 And why is this nonsense?? Because x is public? I don't see the problem
 here.

What's the use-case for mutable non-private members? I would say that it's nonsense if there's no need for them. "Nonsense" may be the wrong word. "Should not be part of D" is more what I really mean.
Sep 11 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

On 9/12/07, Walter Bright <newshound1 digitalmars.com> wrote:
 Janice Caron wrote:
 So the function was declared const, because /conceptually/ it was.

 But the class had a mutable cache, declared with the C++ keyword

 Transitivity would wreck that.

You're right in that transitive const does not support the notion of "logical const" (which is the usual term for what you are referring to). The problem with logical const, however, is it offers no semantic guarantees. I pass a logical const reference around, and the underlying data may or may not change. I have no guarantees one way or the other,

But you could guarantee that /non-private/ data will not change. And since non-private data is never seen outside the file in which it is declared, I don't understand why this is a problem. and even worse, I can't even tell this is happening. "I" here meaning
 the compiler, and our hapless code auditor.

Providing you restrict "logical constness" to private variables, is that really true? Inside the module, the compiler knows everything. Outside the module, the private variables are irrelevant anyway as they can never be accessed in any way. This just pulls the rug out from under:
 1) functional programming

Functional programming isn't possible anyway unless the function can also guarantee that it won't modify /global/ variables, and I see no way of specifying that in the prototype. 2) multithreaded programming Likewise, a function cannot be guaranteed to be completely threadsafe unless it guarantees not to modify global variables, and I see no way of specifying that in the prototype. Even if you could, it's more complicated than that. A function might modify global variables and yet still be threadsafe - providing it uses mutexes to ensure it has exclusive access to the shared variables. A good example of this is writef(). The writef() function writes to standard output, and so /must/ modify some global state - but it's thread-safe because file access is all mutex locked. Moreover, multithreaded programming might /require/ you to lock a mutex, do something, then unlock said mutex. The mutex itself needs to be modifiable - that is, "logically const". More thought needs to be put into that one. 3) having a tightly specified interface Interfaces are my concern. If an Interface specifies that a function be const (in the sense of non modifying member variables), then it would make a lot of sense that classes which implement that interface be allowed to assume the interface means "logical constness", for the reasons of all the use-cases given so far. It goes back to painting a stripe across your hips and calling it a
 seatbelt.

 Given this, it isn't any surprise that C++ is disastrously difficult to
 write functional & multithreaded programs in, and C++ mutability is one
 of the reasons why. In C++, you can write const this and const that and
 it doesn't mean jack squat. Many experienced C++ programmers will tell
 you that const is little more than a documentation aid.

I /am/ an experienced C++ programmer, and I agree. However, in C++, it is possible to declare non-private members mutable - and to make matters worse, the function bodies of functions which modify that variable could be in any number of different source files scattered all over the place. In D it's different. First off, I do suggest restricting "logical constness" to private variables only. Secondly, all the function defintions which can access those source files are in one file, so the compiler knows everything. This means that, in D, when you declare a function as const, there would still be an absolute guarantee that nothing non-private could ever be changed by that function. Outside the module, that's all you need to know. Inside the module, the compiler knows all anyway. In other words, you're quite right that transitive const totally wrecks
 using const to specify logical constness. And that's a good thing <g>.

But logical constness is a /necessary/ thing, and if you outlaw any way of doing it legitimately, then people will do it by "cheating". By (for example), storing state in global variables. The simplest example I can come up with is that random number one. Recall: class RandomNumberGenerator; { private long seed; const int rand() /* guarantees not to modify any non-private member variables */ { seed = f(seed); return (seed >> 32); } } So then I'd have to change it to something like: private long seed; private long getSeed() { lockMutex(); long s = seed; unlockMutex(); return s; } class RandomNumberGenerator; { const int rand() { seed = f(getSeed()); return (seed >> 32); } } And I'd end up with something that was much nastier than the very thing you are trying to avoid.
Sep 12 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

I'm fair sure that logical const is a real world requirement.

Take the classic example Shape, for example...

const Rectangle r = new Shape();
r.draw();

Whoops! Const-transitivity prevents r.draw() from compiling. Why? Because
Shape has a member variable Raster, and Rectangle.draw() calls
Raster.paintRectangle() or some such, which modifies the state of the
raster.

So what do you do? You could try changing the prototype to
class Rectangle { override invariant draw(Raster raster); }

But now it still won't compile, because the abstract function
Shape.drawwasn't declared like that, so then you have to go back
another step and
change /that/ declaration to:
class Shape { abstract invariant draw(Raster raster); }

It's a solution which doesn't scale.

And there goes encapsulation...
Sep 12 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

Even something as simple as this needs logical const

class MyMathClass
{
    invariant int multiply(int x, int y) /* logically const */
    {
        debug logfile.writeLine("multiply.called");
        return x * y;
    }

    debug private Stream logfile;
}

You can't tell me that's not a real world need.
Sep 12 2007
prev sibling next sibling parent reply "Janice Caron" <caron800 googlemail.com> writes:
 You obviously missed the discussion of the "pure" keyword, borrowed from
Fortran-90 from the conference. This is a clear way of declaring that a
function must not have any side-effects.

Cool! But then, I don't understand Walter's objections to "logical constness". Seems pretty simple to me. If a function is logically const but not truly const, then don't declare it pure. Conversely, if it's not declared pure, then it can have logical constness. Isn't that problem solved?
Sep 12 2007
parent reply Bruce Adams <tortoise_74 yeah.who.co.uk> writes:
Janice Caron Wrote:

 You obviously missed the discussion of the "pure" keyword, borrowed from
Fortran-90 from the conference. This is a clear way of declaring that a
function must not have any side-effects.

Cool! But then, I don't understand Walter's objections to "logical constness". Seems pretty simple to me. If a function is logically const but not truly const, then don't declare it pure. Conversely, if it's not declared pure, then it can have logical constness. Isn't that problem solved?

Getting constness and purity mixed up is liable to open a whole new can of worms. I humbly suggest you don't go there. I not sure whetherpure functions can modify class variables. The presentation states that they can't modify anything reachable through their arguments. If we interpret "this" as a argument then pure methods must also be const in the C++ sense.
Sep 12 2007
parent Ingo Oeser <ioe-news rameria.de> writes:
Bruce Adams wrote:

 I not sure whether pure functions can modify class variables. 

I hope not!
 The 
 presentation states that they can't modify anything reachable through
 their arguments. 

They are like mappings (e.g y = f(x) ) in math. If you write sth. like this: b = foo; for(i = 0; i < rand_range(1,1000); i++) { a = pure_func(b); } writefln("%s", a); b will still contain foo and a will always have the same value in every program run. pure_func will only be called once here. This is the possible compiler optimisation, if pure_func() is a pure function. Best Regards Ingo Oeser
Sep 12 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
 All I can find on the net about COW programming is the COW programming
 language.  I'm sure this isn't what you meant :)  can you please explain
 this further?

I suspect he meant "copy on write", but forget to tell us. :-)
Sep 12 2007
prev sibling next sibling parent reply "Janice Caron" <caron800 googlemail.com> writes:
 I suspect he meant "copy on write", but forget to tell us. :-)

Here's an example of copy-on-write: MyString s = "cat"; MyString t = s; /* Only a reference is copied. Now s and t both reference the same data */ writefln(t); /* prints "cat" */ /*drum roll*/ t[0] = 'b'; /* Only now is a copy made. Now t is unique, with s=="cat" and t="bat" */ writefln(s); /* prints "cat" */ writefln(t); /* prints "bat" */ Of course, to implement this - to actually create a class that did this, you'd need MyString to have some internal state (and therefore, either non-transitive const, or logical const).
Sep 12 2007
parent Bruce Adams <tortoise_74 yeah.who.co.uk> writes:
Janice Caron Wrote:

 I suspect he meant "copy on write", but forget to tell us. :-)

Here's an example of copy-on-write: MyString s = "cat"; MyString t = s; /* Only a reference is copied. Now s and t both reference the same data */ writefln(t); /* prints "cat" */ /*drum roll*/ t[0] = 'b'; /* Only now is a copy made. Now t is unique, with s=="cat" and t="bat" */ writefln(s); /* prints "cat" */ writefln(t); /* prints "bat" */ Of course, to implement this - to actually create a class that did this, you'd need MyString to have some internal state (and therefore, either non-transitive const, or logical const).

If you are living in the wonderful world of unix. Copy on write automagically happens for you if you change a value, following a process fork, courtesy of the OS. That's not to say it not a useful technique outside of forking unix processes but the point is that it can happen without any kind of const thrown into the mix. I suspect the restrict keyword interacts with this in interesting ways. Fortunately I've never had to try it myself. Bruce.
Sep 12 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
 "const int void f()" is clearly a bad declaration. Anyway, "const int
 f()" is a function returning a const int, not a const function returning
 an int, so you wouldn't be able to call it through a const reference.

See thread "properties and const" The D syntax for "member function which does not modify member variables" is sort of a bit unclear. In C++ I would have written "int void f() const", but that's not how it goes in D.
Sep 12 2007
prev sibling next sibling parent reply =?ISO-8859-1?Q?Carsten_S=F8rensen?= <cso rift.dk> writes:
Alex Burton wrote:
 While const is being reexamined....
 
 I create an interface :
 
 interface Server
 {
     Data getData() const;
 };

Hello, world! I'm fairly new to the world of D, but a minor detail like that has never stopped me from butting in so far ;) I'm sorry if any of my points have been made before, but I don't really feel like trawling through all 50.000 posts (although I have made an effort to dig into the more recent ones) First of all, I think specifying an interface method to be const is bad practice. The interface is assuming things about a possible implementation that is really not its business. As you describe, this will hinder your implementation. I understand you're not able to specify your particular getData() implementation as const due to const's transitivity, but as far as I'm concerned them's the breaks. I'm a bit of a const purist, sorry. What I would like to see regarding const and interfaces, is that the _implementation_ should be able to guarantee const-correctness even if the interface doesn't, much like covariant return types. The implementation should be free to guarantee more than its interface. That doesn't help you though... Best regards, Carsten Sørensen
Sep 12 2007
parent reply Bruce Adams <tortoise_74 yeah.who.co.uk> writes:
Carsten Sørensen Wrote:

 Alex Burton wrote:
 While const is being reexamined....
 
 I create an interface :
 
 interface Server
 {
     Data getData() const;
 };

Hello, world! I'm fairly new to the world of D, but a minor detail like that has never stopped me from butting in so far ;) I'm sorry if any of my points have been made before, but I don't really feel like trawling through all 50.000 posts (although I have made an effort to dig into the more recent ones) First of all, I think specifying an interface method to be const is bad practice. The interface is assuming things about a possible implementation that is really not its business. As you describe, this will hinder your implementation. I understand you're not able to specify your particular getData() implementation as const due to const's transitivity, but as far as I'm concerned them's the breaks. I'm a bit of a const purist, sorry. What I would like to see regarding const and interfaces, is that the _implementation_ should be able to guarantee const-correctness even if the interface doesn't, much like covariant return types. The implementation should be free to guarantee more than its interface. That doesn't help you though... Best regards, Carsten Sørensen

Someone correct me if I'm wrong but I thought D doesn't / wasn't going to support overloading methods on whether they are const or not. So this part of the discussion is almost moot. Bruce.
Sep 12 2007
parent reply =?ISO-8859-1?Q?Carsten_S=F8rensen?= <cso rift.dk> writes:
Bruce Adams wrote:
 Someone correct me if I'm wrong but I thought D doesn't / wasn't going to
support overloading methods on whether they are const or not. So this part of
the discussion is almost moot.

Sorry if I wasn't totally clear. I'm not talking about overloading, but overriding/implementing and introducing const. Best regards, Carsten Sørensen
Sep 12 2007
parent reply Bruce Adams <tortoise_74 yeah.who.co.uk> writes:
Carsten Sørensen Wrote:

 Bruce Adams wrote:
 Someone correct me if I'm wrong but I thought D doesn't / wasn't going to
support overloading methods on whether they are const or not. So this part of
the discussion is almost moot.

Sorry if I wasn't totally clear. I'm not talking about overloading, but overriding/implementing and introducing const. Best regards, Carsten Sørensen

You were totally clear but using a C++ concept of const methods which I believe (and may be wrong) has no analogue in D. I believe the intention was to keep it out of the language as it is an ambomination. That said, I'm not clear how you can get the proper semantics for e.g. overloads of [] in D without it. Bruce.
Sep 12 2007
parent Nathan Reed <nathaniel.reed gmail.com> writes:
Bruce Adams wrote:
 You were totally clear but using a C++ concept of const methods which I
believe (and may be wrong) has no analogue in D. I believe the intention was to
keep it out of the language as it is an ambomination. That said, I'm not clear
how you can get the proper semantics for e.g. overloads of [] in D without it.
 
 Bruce.

I believe the issue of overloading opIndex and similiar is going to be taken care of using the 'return' storage-class (discussed in WalterAndrei.pdf). Basically it makes the return type of the function const or not, according to whether a particular parameter (e.g. 'this') is const or not. Thanks, Nathan Reed
Sep 12 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
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.

What about all those other cases? There's a long list of applications there.
Sep 12 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On 9/12/07, Janice Caron <caron800 googlemail.com> wrote:
 See, I was basically arguing with you,

That's "with", as in, "on the same side as", not "arguing" as in "disagreeing". Guess I could have worded that better! Ahem. I'll try again... See, I was basically agreeing with you and supporting your claim...
 that transitivity is a good thing

The rest of the post should now make sense!
 -- /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.

Sep 12 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On 9/12/07, Walter Bright <newshound1 digitalmars.com> wrote:

 Because multithreaded programming doesn't start working if the interface
 hides the changing variables. If it did, parallel programming would be
 easy. Logical constness is *still* changing state, and since it is, all
 the boogeymen of synchronization, race conditions, deadlocks, etc., are
 all there.

I've written a lot of multithreaded programming. A lot. ...which is perhaps a bad thing, because sometimes I forget that D does things differently. I don't need to use a separate mutex class if you've got synchronized. (By the way - could you also allow the spelling "synchronised" for the benefit of speakers of British English?) :-) 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? 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. 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.
 Logical constness hides the state change from the awareness of the
 programmer, but it's still changing state.

By definition, yes.
 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? 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.
 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.
 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?
 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. 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. 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.
 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. 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.
 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. 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.
Sep 12 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
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.
Sep 13 2007
prev sibling parent "Janice Caron" <caron800 googlemail.com> writes:
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.
Sep 13 2007