April 03, 2008
"Walter Bright" <newshound1@digitalmars.com> wrote in message news:ft1poo$1js8$1@digitalmars.com...
> Craig Black wrote:
>> This is due to the fact that it has a completely different design goal from C++'s const, based on a hypothetical compiler optimization benefit that no one seems to fully understand.
>
> See http://www.digitalmars.com/d/2.0/const-faq.html#const for what the goals are.

Yeah I understand the concept, but I have doubts as to whether the benefits you speak of will materialize.  Multiprogramming is very complex.  But I hope it will work as you say, and I do think it's worth a try.  I'm also coming to the realization that D's const is not as bad as everyone is making it out to be.  For example, D doesn't provide mutable fields.  I thought this was going to be a big problem, but as Janice pointed out, there are trivial workarounds.  It's similar to how D doesn't provide bitfields, but it does provide std.bitfield, which works just as good.  So I think I'm coming around.

-Craig 

April 03, 2008
On 02/04/2008, Sean Kelly <sean@invisibleduck.org> wrote:
> 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.

One anecdote is not statistically significant. The company I work for once wasted six weeks while six programmers tracked down a multithreading bug, an obscure race condition that brought down our server after some random period in time measured in days. We finally found and fixed it, but it turns out that particular bug could never have happened in D. Transitive const would have stopped it dead. And yes, I know, one anecdote is not statistically significant. I just thought I'd mention, there are other stories.

I have used const_cast, but it is rare. But that's kinda the point - the thing about const_cast is that not that you're supposed to use it, it's that other forms of cast won't accidently cast away constancy. For example:

    class C {};
    const C c;
    C d = static_cast<C>(c); //ERROR

Trouble is, legacy casting still works:

    C e = (C)c; //OK

So the deal is, static_cast<T> is safer than <T> because it preserves const correctness (in C++ terms). const_cast<T> is only necessary for those rare cases where you actually need to change the constancy. At least - that was the theory. In practice, it never worked, because they forgot to deprecate (T).
April 03, 2008
On 03/04/2008, Janice Caron <caron800@googlemail.com> wrote:
>  So the deal is, static_cast<T> is safer than <T>

...than (T)...

Sorry.
April 03, 2008
On 02/04/2008, Sean Kelly <sean@invisibleduck.org> wrote:
> My traditional argument in support of logical const is this:
>
>     class C
>     {
>         mutable mutex monitor;
>         <snip>
>     };

But in D, the class C will already have a mutex, by virtue of the fact that it derives from Object.

...which raises an interesting point. Can "synchronized" be used on a const object? What about an invariant object?
April 03, 2008
On 02/04/2008, Steven Schveighoffer <schveiguy@yahoo.com> wrote:
> Absolutely, but I'd at least like Walter to stop saying that transitive
>  const (read, transitive *keyword* const) is neccessary for pure functions :)
>  If he says "Oh yeah, I guess transitive const isn't necessary, but it's not
>  a priority to fix it right now", then I'd be happy.

But hang on. It's all very well saying "pure functions can access the non-mutable bits only", but in every case I can think of where I might use the mutable keyword in C++, the non-mutable subset of the class is completely useless without the mutable subset.

Can you give me a counterexample of a logically const (muty) class in which the non-mutable subset is actually useful?
April 03, 2008
0) Do I understand correctly, that having on pure member function, class should have _all_ its member functions pure?

1) What about random numbers? rand() can not be pure since 'rand() + rand() != 2*rand()'. However, functional languages do have rand().
And any function, that relies on rand() cannot be pure, too!

Maybe rand() should belong to some other kind of functions, which is not pure, but thread-safe. Thread safety is a great attribute, that I would like to see at my functions. It's a great contract that gives benefits to compiler AND programmer.

2)Suppose, you wrote some math code that operates on Matrices:

class Matrix(int rows, int cols)
{
    private float[rows][cols] elements;

    pure float at(int row, int col)
    in
    {
        assert((0 <= row) && (row < rows));
        assert((0 <= col) && (col < cols));
    }
    body
    {
        return elements[row][col];
    }

    static if (cols == rows) {
        pure float determinant()
        {
            float result;
            /* do some calculus */
            return result;
        }
    }
}

template doSomeStuff(int rows, int cols)
{
    pure invariant (Matrix!(rows,cols)) doSomeStuff(invariant Matrix!(rows,cols) a, invariant Matrix!(rows,cols) b)
    {
        // do some calculus
        return result;
    }
}

It works, you are very glad that your program is parallelized and uses full power of your multi-processor CPU.
But now you want to make some optimizations to speed it up a little. You add some SSE instruction in assembly (it's ok in pure methods, I hope?).
And then you take a step further and it looks like you calculate determinant for your Matrix every time, and it consumes quite some time. You move you determinant calculations to class' ctor and just store the value. But now it turns out that performance degraded dramatically because now you are forced to calculate determinant for every Matrix, even temporary ones, during addition, subtruction etc.

A C++ programmer would add mutable member here:

struct mutable(T)
{
    private T value;

    pure T get()
    {
        return value;
    }

    pure void set(T value)
    {
        mutable* m = cast(mutable*)this;   // yes, it's unsafe, but now programmer
        m.value = value;                   // is responsible for this, not compiler

        // if cast-away is not allowed, then asm would do the trick
    }
}

class Matrix(int rows, int cols)
{
    private float[rows][cols] elements;

    static if (rows == cols) {
        mutable!(float) _determinant = mutable!(float)(float.nan);  // nan == not calculated yet
    }

    pure float at(int row, int col)
    in
    {
        assert((0 <= row) && (row < rows));
        assert((0 <= col) && (col < cols));
    }
    body
    {
        return elements[row][col];
    }

    static if (cols == rows) {
        pure float calcDeterminant()
        {
            float result = 0;
            /* some implementaion follows */
            return result;
        }

        pure float determinant()
        {
            if (_determinant.get() == float.nan) {
                synchronized (this) {
                    if (_determinant.get() == float.nan) {
                        _determinant.set(calcDeterminant());
                    }
                }
            }
            return _determinant.get();
        }
    }
}

This is *transparent* optimization, and it's a programmer now who makes guaranties, that his code makes no side effect. Yes, binary represination of the class is changed, but its logical invariance is preserved. If programmers fails to do it correctly, then it's his fault. By signing his code as pure he claims that the method is thread-safe, doesn't rely on other threads' execution order, calls to it can be rearranged and given the same input it yields the same result.

You might say that this code smells, but it's correct. And it could look slightly better if mutable was not a user-defined template hack, but a language-level feature. You just should expose some restrictions on its usage (like, only private members can be mutable, access to it can be achieved via read-write lock only http://en.wikipedia.org/wiki/Readers-writers_problem).

IMO, mutable is a powerful optimization mechanish, that should be used with *great* care.
April 03, 2008
Janice Caron wrote:
> ...which raises an interesting point. Can "synchronized" be used on a
> const object? What about an invariant object?

"No" for an invariant object, because there is no point to locking something that is invariant. Since an invariant can be implicitly cast to const, that means it can't be allowed for const, either.
April 03, 2008
On Thu, 03 Apr 2008 03:55:47 +0200, Bill Baxter <dnewsgroup@billbaxter.com> wrote:

> They can be run in parallel, as long as you don't stick non-pure mutating operations in between them.
>
> That's quite useful for automatically parallelizing tight loops like this:
>
> nosfx int foo(const C c) { return c.p[0]; }
> ...
>
> C[] array;
> ...// put bunch of C's in array
> int sum = 0;
> foreach(c; array) {
>     sum += foo(c);
> }
>
> Those foreach bodies could be run in parallel (with appropriate reduce logic added to handling of 'sum' by compiler) since we know each call to foo() in that loop has no external side effects.
>
> This is the kind of thing OpenMP lets you do with directives like "#pragma pfor reduce".  Except I don't believe OpenMP has any way to verify that foo() is really side-effect free.
>
> --bb


And could this not be done with real pure functions?

pure int foo(invariant C c) { return c.p[0];}

C[] array;
...// put bunch of C's in array
int sum = 0;
foreach(c; array) {
    sum += foo(cast(invariant)c);
}

We know that c will not change, so we can cast it to invariant.

--Simen
April 03, 2008
On Wed, 02 Apr 2008 16:50:12 +0200, Steven Schveighoffer <schveiguy@yahoo.com> wrote:

> "Simen Kjaeraas" wrote
>> On Wed, 02 Apr 2008 16:41:33 +0200, Steven Schveighoffer  wrote:
>>
>>> "Simen Kjaeraas" wrote
>>>> On Wed, 02 Apr 2008 16:04:36 +0200, Steven Schveighoffer  wrote:
>>>>
>>>>> - a pure method cannot access the mutable portion of a logically
>>>>> invariant data value.
>>>>
>>>> Wouldn't this basically make it transitive invariant?
>>>
>>> Yes, which makes my point :)  pure must be transitive, but const /
>>> invariant
>>> by itself does not need to be.
>>>
>>> -Steve
>>
>> So yes, you can do without transitive const, as long as you define logical
>> const as transitive. I can't quite see what point you're trying to make.
>
> No, I'm not defining logical const as transitive.  I'm defining that pure is
> transitive.  pure functions have nothing to do with requiring const to be
> transitive, which is my point.
>
> Did you look at my example in the original post?  What we have now is
> semantically equivalent to logical const.
>
> -Steve

Yes, there are ways to bypass the const system. We know that, and we accept it. Why not just do:

class foo
{
  int bar;
  const void baz()
  {
    (cast(foo)this).bar++;
  }
}

That also works.

-- Simen
April 03, 2008
Simen Kjaeraas wrote:
> On Thu, 03 Apr 2008 03:55:47 +0200, Bill Baxter <dnewsgroup@billbaxter.com> wrote:
> 
>> They can be run in parallel, as long as you don't stick non-pure mutating operations in between them.
>>
>> That's quite useful for automatically parallelizing tight loops like this:
>>
>> nosfx int foo(const C c) { return c.p[0]; }
>> ...
>>
>> C[] array;
>> ...// put bunch of C's in array
>> int sum = 0;
>> foreach(c; array) {
>>     sum += foo(c);
>> }
>>
>> Those foreach bodies could be run in parallel (with appropriate reduce logic added to handling of 'sum' by compiler) since we know each call to foo() in that loop has no external side effects.
>>
>> This is the kind of thing OpenMP lets you do with directives like "#pragma pfor reduce".  Except I don't believe OpenMP has any way to verify that foo() is really side-effect free.
>>
>> --bb
> 
> 
> And could this not be done with real pure functions?
> 
> pure int foo(invariant C c) { return c.p[0];}
> 
> C[] array;
> ...// put bunch of C's in array
> int sum = 0;
> foreach(c; array) {
>     sum += foo(cast(invariant)c);
> }
> 
> We know that c will not change, so we can cast it to invariant.
> 
> --Simen

That complicates things if you want to modify the C's in the array after the foreach.

--bb