Range checking / bounds (of a variable) checking

Hello,

In my old memories, I recall that Ada (programming language) provide a concept of “range” for checking that a numerical value stored in a variable is always between a lower bound and an upper bound.
This kind of check, is done at run time.
I wonder if this kind of mechanism currently exist in Julia.
For example I would like to say that I store an unsigned integer 8 but that upper bound can’t be upper than 10 and lower bound must be greater than or equal to 1.
I only find information about bound checking for array indices in doc.
Any idea?

Kind regards

Make your own type wrapping the number, implement the methods and provide the checking in an inner constructor.

julia> struct RangeNumber{T<:Number, R}
           v::T

           function RangeNumber(v::Number, r::UnitRange)
               v in r || error("$v not in $r")
               return new{typeof(v), r}(v)
           end
       end

julia> Base.:+(a::RangeNumber{T, R}, b::RangeNumber{T, R}) where {T, R} = RangeNumber(a.v+b.v, R)

julia> Base.:+(a::Number, b::RangeNumber{T, R}) where {T, R} = RangeNumber(a+b.v, R)

julia> Base.:+(a::RangeNumber{T, R}, b::Number) where {T, R} = RangeNumber(a.v+b, R)

julia> a = RangeNumber(3, 1:7)
RangeNumber{Int64,1:7}(3)

julia> a+a
RangeNumber{Int64,1:7}(6)

julia> a+a+a
ERROR: 9 not in 1:7
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] RangeNumber(::Int64, ::UnitRange{Int64}) at ./REPL[1]:5
 [3] + at ./REPL[3]:1 [inlined]
 [4] +(::RangeNumber{Int64,1:7}, ::RangeNumber{Int64,1:7}, ::RangeNumber{Int64,1:7}) at ./operators.jl:502
 [5] top-level scope at none:0

julia> a+3
RangeNumber{Int64,1:7}(6)

julia> a+5
ERROR: 8 not in 1:7
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] RangeNumber(::Int64, ::UnitRange{Int64}) at ./REPL[1]:5
 [3] +(::RangeNumber{Int64,1:7}, ::Int64) at ./REPL[5]:1
 [4] top-level scope at none:0

etc

Very interesting althought I would prefer to also be able to call

RangeNumber{Int64,1:7}(3)

So I can store the “restriction” (ie RangeNumber{Int64,1:7}) and use it later.

Just add the constructor

julia> RangeNumber{T, R}(n) where {T, R} = RangeNumber(convert(T, n), R)

julia> RangeNumber{Int64,1:7}(3)
RangeNumber{Int64,1:7}(3)

julia> RangeNumber{Int64,1:7}(8)
ERROR: 8 not in 1:7

Great! Thanks @kristoffer.carlsson

I’m still a bit stuck up.

I would like to be able to store a restriction (range checking) but also the lack of range checking.

I did

abstract type AbstractRangeNumber end

Number(rn::AbstractRangeNumber) = rn.v


struct NoRangeNumber{T<:Number} <: AbstractRangeNumber
    v::T
    function NoRangeNumber(v::Number)
        return new{typeof(v)}(v)
    end
end


"""
    RangeNumber(v, r)

# Example
a = RangeNumber(3, 1:7)
"""
struct RangeNumber{T<:Number, R} <: AbstractRangeNumber
    v::T

    function RangeNumber(v::Number, r::UnitRange)
        v in r || error("$v not in $r")
        return new{typeof(v), r}(v)
    end
end

RangeNumber{T, R}(n) where {T, R} = RangeNumber(n, R)

Base.:+(a::RangeNumber{T, R}, b::RangeNumber{T, R}) where {T, R} = RangeNumber(a.v+b.v, R)
Base.:+(a::Number, b::RangeNumber{T, R}) where {T, R} = RangeNumber(a+b.v, R)
Base.:+(a::RangeNumber{T, R}, b::Number) where {T, R} = RangeNumber(a.v+b, R)

Base.:(==)(a::AbstractRangeNumber{T, R}, b::AbstractRangeNumber{T, R}) where {T, R} = a.v == b.v
Base.:(==)(a::Number, b::AbstractRangeNumber{T, R}) where {T, R} = a == b.v
Base.:(==)(a::AbstractRangeNumber{T, R}, b::Number) where {T, R} = a.v == b

and tests:

using Nextion: RangeNumber, NoRangeNumber
using Test


@testset "RangeNumber" begin
    @testset "construct / isequal" begin
        a = RangeNumber(3, 1:7)
        @test a == 3
    end

    @testset "addition / overflow" begin
        a = RangeNumber(3, 1:7)
        @test a + a == 6 
        @test_throws ErrorException a + a + a == 9
    end

    @testset "other construct" begin
        a = RangeNumber{Int64, 1:7}(3)
        @test a == 3

        @test_throws ErrorException a = RangeNumber{Int64, 1:7}(8)
    end

    @testset "NoRangeNumber" begin
        a = NoRangeNumber(3)
        @test a == 3
    end

end

but it’s failling for odd reason

$ julia test/test_range_number.jl 
ERROR: LoadError: LoadError: TypeError: in Type{...} expression, expected UnionAll, got Type{Nextion.AbstractRangeNumber}
Stacktrace:
 [1] top-level scope at none:0
 [2] include at ./boot.jl:317 [inlined]
 [3] include_relative(::Module, ::String) at ./loading.jl:1038
 [4] include at ./sysimg.jl:29 [inlined]
 [5] include(::String) at /home/scls/.julia/dev/Nextion/src/Nextion.jl:37
 [6] top-level scope at none:0
 [7] include at ./boot.jl:317 [inlined]
 [8] include_relative(::Module, ::String) at ./loading.jl:1038
 [9] include(::Module, ::String) at ./sysimg.jl:29
 [10] top-level scope at none:2
 [11] eval at ./boot.jl:319 [inlined]
 [12] eval(::Expr) at ./client.jl:399
 [13] top-level scope at ./none:3

The general idea is to be able to store either a restriction or a lack of restriction in numbers.