www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - How to enforce compile time evaluation (and test if it was done at

reply =?UTF-8?Q?Christian_K=c3=b6stlin?= <christian.koestlin gmail.com> writes:
I have a small example, that can be used to express 3601000ms as 1h 1s
(a much more advanced version has already been done by
https://github.com/nordlow/units-d).

I would like to enforce that the precomputation (multiplying and
inverting the list of scale's) is done at compile time.

Is it enough to put up static immutable modifiers?
How can I make sure, that the calculations are done at compile time?

Thanks in advance,
Christian


public struct Unit {
  import std.algorithm.iteration;
  import std.range;

  public struct Scale {
    string name;
    long factor;
  }

  public struct Part {
    string name;
    long v;
    string toString() {
      import std.conv;
      return v.to!(string) ~ name;
    }
  }

  private string name;
  private Scale[] scales;

  public this(string name, Scale[] scales) {
    this.name = name;
    this.scales = cumulativeFold!((result,x) => Scale(x.name,
result.factor * x.factor))(scales).array.retro.array;
  }

  public Part[] transform(long v) immutable {
    import std.array;

    auto res = appender!(Part[]);
    auto tmp = v;
    foreach (Scale scale; scales) {
      auto h = tmp / scale.factor;
      tmp = v % scale.factor;
      res.put(Part(scale.name, h));
    }
    return res.data;
  }
}

Unit.Part[] onlyRelevant(Unit.Part[] parts) {
  import std.array;
  auto res = appender!(Unit.Part[]);
  bool needed = false;
  foreach (part; parts) {
    if (needed || (part.v > 0)) {
      needed = true;
    }
    if (needed) {
      res.put(part);
    }
  }
  return res.data;
}

Unit.Part[] mostSignificant(Unit.Part[] parts, long nr) {
  import std.algorithm.comparison;
  auto max = min(parts.length, nr);
  return parts[0..max];
}

unittest {
  static immutable time = Unit("time", [Unit.Scale("ms", 1),
Unit.Scale("s", 1000), Unit.Scale("m", 60), Unit.Scale("h", 60),
Unit.Scale("d", 24)]);

  auto res = time.transform(1 + 2*1000 + 3*1000*60 + 4*1000*60*60 + 5 *
1000*60*60*24);
  res.length.shouldEqual(5);
  res[0].name.shouldEqual("d");
  res[0].v.shouldEqual(5);
  res[1].name.shouldEqual("h");
  res[1].v.shouldEqual(4);
  res[2].name.shouldEqual("m");
  res[2].v.shouldEqual(3);
  res[3].name.shouldEqual("s");
  res[3].v.shouldEqual(2);
  res[4].name.shouldEqual("ms");
  res[4].v.shouldEqual(1);

  res = time.transform(2001).onlyRelevant;
  res.length.shouldEqual(2);
  res[0].name.shouldEqual("s");
  res[0].v.shouldEqual(2);
  res[1].name.shouldEqual("ms");
  res[1].v.shouldEqual(1);

  res = time.transform(2001).onlyRelevant.mostSignificant(1);
  res.length.shouldEqual(1);
  res[0].name.shouldEqual("s");
  res[0].v.shouldEqual(2);
}
Feb 27
next sibling parent Dukc <ajieskola gmail.com> writes:
On Monday, 27 February 2017 at 19:26:06 UTC, Christian Köstlin 
wrote:
 Is it enough to put up static immutable modifiers?
 How can I make sure, that the calculations are done at compile 
 time?

 ...
 static immutable time = Unit("time", [Unit.Scale("ms", 1),
 ...
An initialization of a static variable (or constant, as in this case) is indeed always done at compile time. Another option would be using an enum storage class. Difference between enum and static or shared immutable is that enum IS a compile time constant, not just a runtime constant initialized at compile-time. That means you can use enums to calculate other compile-time stuff. Template parameters are also calculated at compile time. If they take a value, they are enum values, only difference to normal enums being that their values are defined at template call site. Thus, if you pass an expression to a template argument, you can be sure it's calculated at compile time.
Feb 27
prev sibling parent reply sarn <sarn theartofmachinery.com> writes:
On Monday, 27 February 2017 at 19:26:06 UTC, Christian Köstlin 
wrote:
 How can I make sure, that the calculations are done at compile 
 time?
If you ever have doubts, you can always use something like this to check: assert (__ctfe);
Feb 27
next sibling parent reply sarn <sarn theartofmachinery.com> writes:
On Tuesday, 28 February 2017 at 00:20:05 UTC, sarn wrote:
 On Monday, 27 February 2017 at 19:26:06 UTC, Christian Köstlin 
 wrote:
 How can I make sure, that the calculations are done at compile 
 time?
If you ever have doubts, you can always use something like this to check: assert (__ctfe);
Sorry, "enforce" would more appropriate if you're really checking.
Feb 27
parent reply Joseph Rushton Wakeling <joseph.wakeling webdrake.net> writes:
On Tuesday, 28 February 2017 at 00:22:28 UTC, sarn wrote:
 If you ever have doubts, you can always use something like 
 this to check:

 assert (__ctfe);
Sorry, "enforce" would more appropriate if you're really checking.
if (!__ctfe) assert(false); ... might be the best option. That shouldn't be compiled out even in -release builds.
Feb 28
parent reply =?UTF-8?Q?Christian_K=c3=b6stlin?= <christian.koestlin gmail.com> writes:
On 01/03/2017 00:09, Joseph Rushton Wakeling wrote:
 On Tuesday, 28 February 2017 at 00:22:28 UTC, sarn wrote:
 If you ever have doubts, you can always use something like this to
 check:

 assert (__ctfe);
Sorry, "enforce" would more appropriate if you're really checking.
if (!__ctfe) assert(false); ... might be the best option. That shouldn't be compiled out even in -release builds.
thats a nice idea! is this happening because of assert(false) being always part of release builds (as mentioned here: https://dlang.org/spec/contracts.html#assert_contracts) or because the if would have no instructions anymore if this is removed. cK
Mar 01
parent XavierAP <n3minis-git yahoo.es> writes:
On Wednesday, 1 March 2017 at 09:19:53 UTC, Christian Köstlin 
wrote:
 On 01/03/2017 00:09, Joseph Rushton Wakeling wrote:
 if (!__ctfe) assert(false);
 
 ... might be the best option.  That shouldn't be compiled out 
 even in -release builds.
thats a nice idea! is this happening because of assert(false) being always part of release builds (as mentioned here: https://dlang.org/spec/contracts.html#assert_contracts) or because the if would have no instructions anymore if this is removed.
Yes assert(false) or assert(0) is a special case according to the specification. At least in the DMD implementation it is not removed for -release. If reached it throws an "object.Error (0): assert(0) or HLT instruction" instead of core.exception.AssertError
Mar 01
prev sibling parent reply =?UTF-8?Q?Christian_K=c3=b6stlin?= <christian.koestlin gmail.com> writes:
On 28/02/2017 01:20, sarn wrote:
 On Monday, 27 February 2017 at 19:26:06 UTC, Christian Köstlin wrote:
 How can I make sure, that the calculations are done at compile time?
If you ever have doubts, you can always use something like this to check: assert (__ctfe);
Thanks a lot, actually works as you describe it! As I understand the only difference between assert and enforce is, that assert is not compiled into releases? Thanks! Christian
Feb 27
parent reply sarn <sarn theartofmachinery.com> writes:
On Tuesday, 28 February 2017 at 07:41:36 UTC, Christian Köstlin 
wrote:
 As I understand the only difference between assert and enforce 
 is, that
 assert is not compiled into releases?

 Thanks!
 Christian
Pretty much so. The intention is that assert means something that's supposed to be true (and can be assumed in release) while enforce means something you want to be true (but can't guarantee). E.g., you can assert that a list is sorted after running heapsort on it, but you need to enforce that a file is in the correct format.
Feb 28
parent reply Jonathan M Davis via Digitalmars-d-learn writes:
On Tuesday, February 28, 2017 09:16:47 sarn via Digitalmars-d-learn wrote:
 On Tuesday, 28 February 2017 at 07:41:36 UTC, Christian Kstlin

 wrote:
 As I understand the only difference between assert and enforce
 is, that
 assert is not compiled into releases?

 Thanks!
 Christian
Pretty much so. The intention is that assert means something that's supposed to be true (and can be assumed in release) while enforce means something you want to be true (but can't guarantee). E.g., you can assert that a list is sorted after running heapsort on it, but you need to enforce that a file is in the correct format.
I'd put it more strongly than that. assert is for program invariants. If the condition is true, your program is outright broken. For performance reasons, assertions are removed in release builds, but many folks think that they should be left in and kill our program if they fail in release (and would be why some folks won't compile with -release even in production). assert(0) _does_ stay in your program in release mode and could be used in conjuction with an if statement (since it doesn't have a condition to test), but it's intended for unreachable code. Normally, assert throws an AssertError on failure (assert(0) becomes an HLT instruction in release), and types derived from Error are intended to kill your program on failure, precisely because they indicate a bug in your program and if they fail, it means that your program is in an undefined state (RangeError, which is use a failed bounds check for an array would be another example of an Error). Exceptions - specifically Exception or any type derived from it - are for general error conditions that occur while a program is running but do not necessarily indicate a bug in the program. Bad user input, stuff on disk, anything in the environment, etc. - i.e. anything over which you have no control - generally would be the sort of thing that triggers an exception when the program hits them. Exceptions are part of the normal operation of your program and are expected to be recoverable (though a typical program that hits no bad input would typically never need to throw an exception). In general, the distinction between assertions and exceptions should be very clear: assertions are for program invariants and exceptions are for when your program encounters bad input or any other error condition that does not indicate a bug in your program. Unfortunately however, the fact that enforce looks the same as assert does seem to confuse some folks. The only place that gets a bit debatable is testing arguments to a function, and that really depends on the function's contract. If the function requires that its input meet some conditions, and it's a bug if the caller does not meet them, then an assertion should be used, whereas if it's not a bug in the program if the caller provides bad input, then an exception is appropriate. And when it should be considered a program bug and when it should just be considered normal program execution depends on exactly what the function is doing as well as your programming style (e.g. some folks prefer that the caller be required to verify the correct input, whereas others prefer that the callee do that; there are pros and cons to both). So, that can get subjective. But ultimately, assertions are still for program bugs, whereas exceptions are for normal error conditions that do not indicate a bug in the program. - Jonathan M Davis
Mar 01
parent reply Dukc <ajieskola gmail.com> writes:
On Wednesday, 1 March 2017 at 16:43:41 UTC, Jonathan M Davis 
wrote:
 Assert is for program invariants. If the condition is true, 
 your program is outright broken.
Error: He meant that if the condition is FALSE, the program is faulty.
Mar 01
parent Jonathan M Davis via Digitalmars-d-learn writes:
On Wednesday, March 01, 2017 17:02:37 Dukc via Digitalmars-d-learn wrote:
 On Wednesday, 1 March 2017 at 16:43:41 UTC, Jonathan M Davis

 wrote:
 Assert is for program invariants. If the condition is true,
 your program is outright broken.
Error: He meant that if the condition is FALSE, the program is faulty.
LOL. True. Sorry about that. Invariants must be true, not false. I should have caught that. - Jonathan M Davis
Mar 01