As an old C programmer, I use lots of asserts in my code. Now I want to globally switch them off to speed things up. What is the best practice way to do that?
3 Answers
There is no built-in option / command line flag to disable @asserts globally, yet(!).
For now, you can define a @myassert macro which, depending on a global switch, is a no-op or a regular @assert:
asserting() = false # when set to true, this will enable all `@myassert`s
macro mayassert(test)
esc(:(if $(@__MODULE__).asserting()
@assert($test)
end))
end
f(x) = @mayassert x < 2
(taken from https://discourse.julialang.org/t/assert-alternatives/24775/14)
- 9,177
- 1
- 24
- 39
Although it would be nice to have this feature, the need for @asserts in your code can be reduced by defining and dispatching on your own types. For example, suppose you have a function foo(t::TimeType) = t, but you only want to accept times that are a multiple of five minutes. You can create a new type with this requirement:
using Dates
struct FiveMinuteMultiple
t::DateTime
function FiveMinuteMultiple(y, m, d, h, mi)
if mi%5 != 0
throw(DomainError(m, "the minute argument must be a multiple of 5"))
end
new(DateTime(y, m, d, h, mi))
end
end
Now you literally can't create a FiveMinuteMultiple that is not a multiple of five minutes:
julia> t = FiveMinuteMultiple(2016, 7, 15, 4, 23)
ERROR: DomainError with 7:
the minute argument must be a multiple of 5
Stacktrace:
[1] FiveMinuteMultiple(::Int64, ::Int64, ::Int64, ::Int64, ::Int64) at ./REPL[2]:5
[2] top-level scope at none:0
julia> t = FiveMinuteMultiple(2016, 7, 15, 4, 25)
FiveMinuteMultiple(2016-07-15T04:25:00)
So if you now define foo(t::FiveMinuteMultiple) = t, you no longer need an @assert to verify that the argument is a time that is a multiple of five minutes. Sure, you still have to pay the cost of argument checking when you construct the FiveMinuteMultiple, but unless it's a hot inner loop you probably want that additional data validation anyways.
Advantages:
- Method dispatch guarantees that the arguments to your functions are of the correct type.
- You can avoid duplicating the same assertion across multiple functions
foo(t::FiveMinuteMultiple),bar(t::FiveMinuteMultiple), andbaz(t::FiveMinuteMultiple). - The more specific argument annotation alerts users and developers that the function expects a more specific type of data.
Disadvantages:
- Depending on your use case, you may need to forward various methods to the data field within your struct. For example, for
FiveMinuteMultipleyou may need to forward methods such asday,hour, etc, to thetfield of the struct. - Adding a new concept (type) to represent assertions about your data might introduce an unnecessary layer of abstraction.
- 6,393
- 17
- 32
-
1Creating custom types to avoid asserts seems like a bit of an overkill to me. I wouldn't introduce multiple Float64 wrapper types to replace a bunch of floating point value assertions. – carstenbauer Oct 09 '19 at 17:31
-
Of course it depends on the context. I think there are some situations were this pattern makes sense. One advantage is that the more specific argument annotation alerts the user that the function expects a more specific type of data. – Cameron Bieganek Oct 09 '19 at 17:44
-
2I use asserts to document assumptions made in the code. These are things that the code assumes "couldn't possibly be true". This is different from expected errors such as bad arguments to an API. All code makes assumptions, and they are sometimes wrong. It is good to test these limits, but also to globally switch the tests off when you ship – opus111 Oct 09 '19 at 18:49
-
1I'm not sure what "couldn't possibly be true" means. If that were the case, then you wouldn't need assertions. Maybe the distinction is between user errors and developer errors? – Cameron Bieganek Oct 09 '19 at 19:16
-
1Asserts document assumptions made by the developer when writing the code. See here for a good writeup https://stackoverflow.com/questions/1081409/why-should-i-use-asserts – opus111 Oct 09 '19 at 19:24
-
Ah, I see. I had encountered a similar discussion before, but I kinda forgot the distinction since I don't use asserts much. Assertions are a tool for developers to help with debugging and ensuring internal consistency of their software. Exceptions are for handling user or runtime errors. – Cameron Bieganek Oct 09 '19 at 19:48
You could put your @assert statements in a @debug block. Then the @assert call is desactivated, unless you activate debugging either globally (ENV["JULIA_DEBUG"] = "all") or just for your module (ENV["JULIA_DEBUG"] = "NameOfYourModule")
julia> @debug begin
@assert 1==2
end
#or
@debug @assert 1==2 # assert is not called
julia> ENV["JULIA_DEBUG"] = "all" # enable debugging
"all"
julia> @debug begin
@assert 1==2
end
┌ Error: Exception while generating log record in module Main at REPL[4]:1
│ exception =
│ AssertionError: 1 == 2
│ Stacktrace:
│ [1] top-level scope at REPL[4]:2
│ [2] top-level scope at logging.jl:319
| ...
└ @ Main REPL[4]:1
- 1,719
- 10
- 15
-
Unfortunately, violation of the assertion no longer leads to program termination, but rather the log message `Error: Exception while generating log record in module`. – Jim Garrison Sep 10 '20 at 13:54