Tuesday, March 16, 2010

Assert, Require, Assume

Very simple but useful are the methods assert, require and assume which are built into the Predef object. As you might expect they are methods for performing certain checks during runtime to verify certain conditions. They do not use the Java assert framework and therefore are always evaluated regardless of whether or not assertions are enabled.

Update: Scala 2.8 has an annotation called elidable that will (when 2.8 is complete) allow one to remove method calls at compile time by setting a compiler flag. The assert, etc... methods are all marked with this flag and as a result can be removed at compile time for production environments.

  1. scala> var called = 0
  2. called: Int = 0
  3. scala> called
  4. res0: Int = 0
  5. /*
  6. assert, require and assume have call by name parameters so the message is only 
  7. calculated when the assertion fails.
  8. */
  9. scala> assert (called == 0, {called += 1; println("called is not 0")})
  10. scala> require (called == 0, {called += 1; println("called is not 0")})
  11. scala> assume (called == 0, {called += 1; println("called is not 0")}) 
  12. scala> called = 1
  13. called: Int = 1
  14. // demonstrating that the method is in fact called when the assertion fails
  15. scala> assert (called == 0, {called += 1; println("called is not 0")}) 
  16. called is not 0
  17. java.lang.AssertionError: assertion failed: ()
  18. at scala.Predef$.assert(Predef.scala:95)
  19. ...
  20. scala> called
  21. res4: Int = 2
  22. /*
  23. Require is intended to be used as a precondition of a method so 
  24. it throws an IllegalArgumentException, not an AssertionError
  25. */
  26. scala> require (called == 0, {called += 1; println("called is not 0")})
  27. called is not 0
  28. java.lang.IllegalArgumentException: requirement failed: ()
  29. at scala.Predef$.require(Predef.scala:117)
  30. ...
  31. scala> called                                                          
  32. res6: Int = 3
  33. scala> assume (called == 0, {called += 1; println("called is not 0")}) 
  34. called is not 0
  35. java.lang.AssertionError: assumption failed: ()
  36. at scala.Predef$.assume(Predef.scala:107)
  37. ...
  38. scala> called                                                         
  39. res8: Int = 4

scala 2.7.7
  1. /*
  2. In Scala 2.7 the parameter is evaluated before the 
  3. method is called so the side effect of the message causes
  4. the assertion to fail
  5. */
  6. scala> assert (called == 0, {called += 1; println("called is not 0")})
  7. called is not 0
  8. scala> called
  9. res2: Int = 1


  1. This of course leaves us wondering what the difference between assert and assume is.

  2. There is no real difference. It just reads different.

  3. I didn't realise assert() was always enabled. It's pretty important to be able to disable asserts: is there a way?

  4. In fact there is a way. I will address it in my next post. It is elidable compiler level Certain methods can be annotated so they will not be compiled given a certain compiler flag

  5. Hmm seems the elidable compiler flags are not yet available in the snapshot. So I will wait on that post. But the idea is to be able to pass a flag to the compiler and certain method calls will be removed by the compiler to not have the overhead

  6. Jesse - elidable has been in there for months and I just confirmed it's working fine. Don't know what issue you're having, but see the test case I just checked in if you need example usage.

  7. Shouldn't that be "It's pretty important to not disable asserts". In Java I never use the assert statement because it is (by default) disabled in the field. Instead I use my own methods that can't be disabled. Often it is as important or more so to detect errors that occur in production as it is to detect errors during testing. For one thing, if errors are found in production, it indicates that there is a problem with your testing practices -- something you would probably want to know about.