Fully transitive const is not necessary

Koroskin Denis 2korden at gmail.com
Thu Apr 3 01:45:47 PDT 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.



More information about the Digitalmars-d mailing list