Walter:

> Saying it is a safe way to break purity assumes that there was no purpose to 
> the purity.
> There is no guaranteed safe way to break purity, with or without a compiler 
> switch.

The compiler switch I am talking about doesn't break purity. Its purpose is 
similar to removing the "pure" attributes from the source code. And doing it is 
usually safe, if you do it in the whole program.

If you take a D2 program, you remove all its pure attributes and you compile it 
again, the result is generally a program just as correct as before. The 
compiler just doesn't perform the optimizations that purity allows: a function 
that's now not pure gets computed each time you call it, it doesn't use the 
pure GC Don was talking about, the (future) conditionally pure higher order 
functions just return a not pure result, it keeps not using global mutable 
state because you have not modified the source code in any other way. 

If you recompile all the program parts that use the functions that are now not 
pure, the only things that break are static constraints (or static asserts) 
that require a function pointer to be pure, and similar things, but I have not 
used such things so far, they are probably quite uncommon.

An example, I have a little buggy strongly pure function sqr that's pure. The 
smart compiler moves the call to sqr out of the foreach loop because sqr is 
pure and x doesn't change inside the loop:

import std.stdio: writeln;

pure int sqr(const int x) {
  int result = x * x * x; // bug
  version (mydebug) writeln(result);
  return result;
}

void main() {
  int x = 10;
  int total;
  foreach (i; 0 .. 10)
    total += sqr(x);
  writeln(total);
}

To debug sqr I use the -nopure switch, its purpose is just to virtually comment 
out all the "pure" annotations. I also compile with -version=mydebug (I have 
avoided debug{} itself just to avoid confusion):


import std.stdio: writeln;

/*pure*/ int sqr(const int x) {
  int result = x * x * x; // bug
  version (mydebug) writeln(result);
  return result;
}

void main() {
  int x = 10;
  int total;
  foreach (i; 0 .. 10)
    total += sqr(x);
  writeln(total);
}

Now the writeln() inside sqr works and its purity is not broken, it's just 
absent, and the compiler just calls sqr ten times because sqr is not pure any 
more.


On the other hand if you have a program like this:

import std.stdio, std.traits;

pure int sqr(const int x) {
  int result = x * x * x; // bug
  version (mydebug) writeln(result);
  return result;
}

auto pureApply(TF, T)(TF f, T x)
if (functionAttributes!f & FunctionAttribute.PURE) {
    return f(x);
}

void main() {
  int x = 10;
  writeln(pureApply(&sqr, x));
}

If you compile this program with -nopure it breaks, because pureApply() 
requires f to be pure. I think this kind of code is uncommon.

Bye,
bearophile

Reply via email to