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