I want to do some things with non-negative integers and use it in function signatures. Is this idiomatic?
# Concrete subtypes should have a `val` attribute.
abstract type NonNegativeInteger <: Integer end
struct NonNegativeInt{T} <: NonNegativeInteger
val::T
end
Base.:+(x::T, y::T) where T <: NonNegativeInteger = T(x.val + y.val)
Base.:*(x::T, y::T) where T <: NonNegativeInteger = T(x.val * y.val)
Base.:^(x::T, y::T) where T <: NonNegativeInteger = T(x.val ^ y.val)
a = NonNegativeInt(2)
b = NonNegativeInt(3)
(a + b, a * b, a ^ b)
There isn’t such a type for floating-point numbers; you could define one, of course, but this is something that’s probably better done with a runtime argument check:
function f(x::Real)
x ≥ 0 || throw(DomainError("negative x=$x not allowed"))
...
end
Note that if x is an Unsigned type (like UInt, and also things like Rational{UInt}), the compiler will know that x ≥ 0 is always true and optimize out the check.
≥ 0 is not a special case, in the sense that the compiler (LLVM) does constant folding and dead-code elimination on any predicate that is inlined (any sufficiently short function). Whether this results in an elided check depends on what you are doing and the limits of LLVM.