April 02, 2008
Janice Caron wrote:
> However, I suspect that the following will be OK.
> 
>     enum g = 42;
> 
>     pure int f()
>     {
>         return g;
>     }

Yes, because the value of g can never change.

The way to think about pure functions is that if the arguments are the same, the result will be the same. This precludes reading any global state that could change, and precludes writing to any global state.

But global invariants and manifest constants cannot ever change state, and cannot be written to, so using them in a pure function is ok.
April 02, 2008
Bill Baxter wrote:
> So const isn't a guarantee of thread safety.

You're right, it isn't sufficient. But it is necessary.


> Hey! That probably *is* thread safe, and it didn't even use const at all.

You can certainly write thread-safe code that doesn't use const at all. You just have to be careful not to change it - i.e. the checking will be  done by you, the programmer, rather than the compiler.

> I can see invariant having some value in conjunction with pure, but I also suspect that invariant, being so strict, is also something that will not get used a lot outside of simple constants and code carefully designed for running in parallel.  Like what Don's said.

Invariant strings have turned out to be a resounding success (and I was very, very skeptical of that initially). I suspect that more and more programs will gravitate towards using invariant as people get more used to the idea. I know my programs will.
April 02, 2008
Steven Schveighoffer wrote:
> HOWEVER, the point that everyone is arguing is why does logical const make pure functions or functional programming impossible?  Clearly, it is ALREADY POSSIBLE to have logical const, and clearly, pure functions are possible! I'm saying transitive const is mathematically equivalent to logical const ALREADY.  Please try and grasp that concept.

You do not need const at all to do function programming. But if you do that, you'll give up all compiler help. What const/invariant does is enable the compiler to enforce certain guarantees.

Multiprogramming is notoriously difficult in languages that provide no language guarantees (like C++) and is fairly straightforward in languages that do provide guarantees (like Erlang). There has been recently a huge surge in interest in Erlang and Haskell for multiprogramming, and they are willing to put up with all the other faults in those languages, because people are able to get their multiprograms to work reliably in those languages without herculean efforts.
April 02, 2008
== Quote from Walter Bright (newshound1@digitalmars.com)'s article
> Sean Kelly wrote:
> > I agree that transitive const can achieve more demonstrably correct code
> > but I don't think it follows that this will necessarily improve productivity.
> > My experience with const in C++ is that it actually reduces productivity
> > because I spend more time dealing with the misapplication of const than
> > I gain from whatever bugs the application of const may have prevented.
> > In fact, in all my time as a programmer I can't think of a single instance
> > where I've encountered a bug that could have been prevented through
> > the use of const.
> C++ const is more of a suggestion than anything verifiable by the compiler and static analysis.

I know you like to talk about the unreliability of const in C++ because of const_cast and the like, but in my opinion those are theoretical objections because I've never encountered even a single use of const_cast in actual code.  So while I do agree that C++ const may not be useful for static analysis, I also believe that C++ const works just fine for achieving more demonstrably correct code.  For example, if I create this class:

    class BoxedInt
    {
        int val;
    public:
        BoxedInt( int v ) : val( v ) {}
        int get() const { return val; }
        void set( int v ) { val = v; }
    };

I am confident that if someone tries to call set() on a const instance of
BoxedInt a compile-time error will occur.  To me, this means that C++
const helps to write demonstrably correct code because the compiler
will trap at least some logic errors related to intended program behavior.
Sure, the user could theoretically cast away const-ness of his BoxedInt
object and thus break everything, but as far as I'm concerned that's his
problem.

> This will become more obvious as C++ tries
> to compete in the multiprogramming arena. The C++ compiler *cannot* help
> with multiprogramming issues; the C++ programmer is entirely dependent
> on visual inspection of the code, making C++ a tedious, labor intensive
> language for multiprogramming.

I agree with this.  C++ const is intended to catch logical mistakes and is not useful for multiprogramming.

> How many times have you looked at the documentation for a function API, and wondered which of its parameters were input and which were output?

Never.  Though it perhaps helps a bit that C++ classes are value types. I know that counter-examples could be provided along the lines of:

    struct C
    {
        char* buf;
    };

    void fn( const C val )
    {
        val.buf[0] = 0;
    }

But I've never seen anyone actually make this kind of mistake.  Perhaps I've just been lucky.

> When you talk about the "misapplication" of const, does that mean failure to understand what the specific API of a function is?

Most often, the problem I've seen is for const to not be consistently applied
to class member functions or within the program itself.  It's been a while so
I'm actually fuzzy on what the exact problems were, but I remember modifying
a bunch of unrelated code when I created a new member function in some class.
If I had to guess I'd say this had something to do with the non-transitive nature
of const in C++ and that the class contained pointers which were being used in
questionable ways.

In fact, here's one such example that I know I encountered, though it may not
be the exact situation I'm recalling above.  It does involve some minor refactoring
so it's not exactly a "misapplication" of const however:

    class C
    {
        D* d;
        E* e;
    public:
        C () : d ( new D ), e( new E ) {}
        D* getD() const { assert( d ); return d; }
        E* getE() const { assert( e ); return e; }
    };

    class D
    {
    public:
        char* getName() const { ... }
        int getAge() { ... }
    };

    void useC( C const& c )
    {
        C c;
        D* d = c.getD();
        assert( d );
        printf( "%s %d\n", d->getName(), d->getAge() );
    }

I wanted to add a new get method to this class but noticed that, based on the
class' design there was no reason for it to return pointers.  The contained objects
were owned by C and they were guaranteed to be non-null, so why not let the
class interface reflect that.  In exchange the compiler would catch some stupid
mistakes on the user side and the user would be able to do away with their
null checks.  So:

    class C
    {
        D* d;
        E* e;
        F* f;
    public:
        C () : d ( new D ), e( new E ), f( new F ) {}
        D const& getD() const { return *d; }
        E const& getE() const { return *e; }
        F const& getF() const { return *f; }
    };

The obvious problem being that the class is now returning const references
where it could previously return pointers to non-const data.  This was entirely
fine within the context of what was actually being done, but the methods in D,
E, and F weren't labeled as const in a consistent manner.  So I basically ended
up having to change code all over the place because C++ does not have
transitive const.  Obviously, the above issue won't exist in D however, and
the other situations I can't recall may be quite similar to this and thus a non-
issue in D.  I wish I could recall more exactly.


Sean
April 02, 2008
== Quote from Walter Bright (newshound1@digitalmars.com)'s article
> If you do away with transitive const, you cannot have invariant either.
> Const allows you to reuse code that works on invariants to work with
> mutables, too.
> Logical const just utterly breaks the whole system. Every scheme we've
> looked at for logical const winds up in some way breaking invariants. If
> invariants are broken, the advantages of them all just go away. I
> suspect that logical const is like perpetual motion - if you think
> you've solved it, you've just made a mistake somewhere <g>.

My traditional argument in support of logical const is this:

    class C
    {
        mutable mutex monitor;
        std::string name;
    public:
        std::string name() const
        {
            scoped_lock sl( monitor );
            return name;
        }

        void name( std::string const& n )
        {
            scoped_lock sl( monitor );
            name = n;
        }
    };

Here, the mutex has nothing to do with the state of the object and must be modified even during logically non-modifying operations. Similar behavior might be necessary for a logging system in some instances, etc.  However, I'm not certain whether this is sufficient to justify logical const in general.  As you say--it has some serious problems as well.


Sean
April 02, 2008
Sean Kelly wrote:
> My traditional argument in support of logical const is this:
> 
>     class C
>     {
>         mutable mutex monitor;
>         std::string name;
>     public:
>         std::string name() const
>         {
>             scoped_lock sl( monitor );
>             return name;
>         }
> 
>         void name( std::string const& n )
>         {
>             scoped_lock sl( monitor );
>             name = n;
>         }
>     };
> 
> Here, the mutex has nothing to do with the state of the object and
> must be modified even during logically non-modifying operations.
> Similar behavior might be necessary for a logging system in some
> instances, etc.  However, I'm not certain whether this is sufficient
> to justify logical const in general.  As you say--it has some serious
> problems as well.

If C.name were invariant, there would be no need for any locks. I don't think this is a good example, because with D's invariant strings there is no need for such locking.
April 02, 2008
Janice Caron wrote:
> We are suffering from a communications difficulty caused by you and I
> using the same phrase ("logical const") to mean entirely different
> things. Unless we can agree on a common terminology, we're not going
> to be able to get anywhere with this discussion.

You're right. This thread will go nowhere as long as "logical const" isn't defined.

My understanding of logical const, and the meaning I use of it, is the C++ notion of a class that uses non-static fields declared as "mutable" to implement a class that appears to be const from the user's perspective, but actually has changing field values.

Mutating a static member is not logical const.
April 02, 2008
Walter Bright wrote:

> If you do away with transitive const, you cannot have invariant either. Const allows you to reuse code that works on invariants to work with mutables, too.
> 
> Logical const just utterly breaks the whole system. Every scheme we've looked at for logical const winds up in some way breaking invariants. If invariants are broken, the advantages of them all just go away. I suspect that logical const is like perpetual motion - if you think you've solved it, you've just made a mistake somewhere <g>. I also suspect that the failure of logical const validates the D scheme of invariants as being correct - there shouldn't be any holes in it.
> 
> You're right that invariant by itself is not enough to specify a pure function, a pure function also cannot read or write global state. But invariant is still a necessary condition, it's just not sufficient.

Is it possible to describe what tricks the compiler can do given:
 A. transitive const      w/ mutable access to globals/statics
 B. transitive invariance w/ mutable access to globals/statics
 C. transitive const      w/ const access to globals/statics
 D. transitive invariance w/ invariant access to globals/statics

It'd be extremely helpful to have that kind of discussion in the const FAQ. Without it, I feel like we're all discussing theoretical stuff with little basis for our arguments.

PS: I went to digitalmars and couldn't find the const FAQ.  It's not linked to from the normal FAQ and is not included in the left-side bar on the D 2.0 page(s).




> 
> Peoples' troubles with const seem to stem from attempting to defeat it in one way or another. While defeating const is legal and trivial in C++, D tries to close those doors.


April 02, 2008
Steven Schveighoffer wrote:
> The communications gap is not in that I do not understand what logical const is.  The communications gap is that you are not understanding that what I have posted is semantically equivalent to logical const.

A static member is not semantically equivalent to a non-static member, that's why both are supported. A static member is part of global state, not the state of the object itself.
April 02, 2008
Walter Bright wrote:
> Janice Caron wrote:
>> We are suffering from a communications difficulty caused by you and I
>> using the same phrase ("logical const") to mean entirely different
>> things. Unless we can agree on a common terminology, we're not going
>> to be able to get anywhere with this discussion.
> 
> You're right. This thread will go nowhere as long as "logical const" isn't defined.
> 
> My understanding of logical const, and the meaning I use of it, is the C++ notion of a class that uses non-static fields declared as "mutable" to implement a class that appears to be const from the user's perspective, but actually has changing field values.
> 
> Mutating a static member is not logical const.

By using a global you can come up with something that is operationally equivalent to that definition of logical const.  So it made sense to also call that "logical const" as a short hand for "operationally equivalent to logical const using globals".  I'm not sure why anyone found that so terribly confusing.  But there's nothing wrong with being a little more precise, though we still don't have a good name for "operationally equivalent to logical const by use of globals".  Other than "globby" I mean. :-)

--bb