zero
does’t work for this reason.
That is correct. I have checks in place to guarantee that I won’t access undefined variables. The same you would do in C, if you like embracing the thrill of undefined behavior as much as I do
I specifically want all my bounded/unbounded/open/closed intervals to be of the same type Interval{T}
, capturing only the nature of the underlying total order T
we are working in, and not the “kind” of the interval. This is different from what is done in IntervalSets
with
struct Interval{L,R,T} <: TypedEndpointsInterval{L,R,T}
left::T
right::T
Interval{L,R,T}(l, r) where {L,R,T} = ((a, b) = checked_conversion(T, l, r); new{L,R,T}(a, b))
end
The reason is that I have to work with collections of intervals of different kinds, and storing them in a heterogeneous array Vector{Interval{L,R,Int} where {L,R}
incurs a performance overhead (~10x), which I want to get rid of.
The only other possibility that comes to my mind is to always store the endpoint in left
for both [x,∞)
and (-∞,x]
(which among left_kind
and right_kind
is :unbounded
disambiguates between the two). But that is just nasty and very error prone in the rest of the code. Everything would be so simple if I could just initialize only right
and not left
.
What do I mean with this last approach?
This is what I mean by “always store in left
”:
function Interval{T}(
left_kind::Symbol,
right_kind::Symbol,
value::T,
checks::Val{Checks} = Val(true),
)::Interval{T} where {T,Checks}
if Checks
left_kind == :closed ||
left_kind == :open ||
left_kind == :unbounded ||
error("invalid left endpoint kind")
right_kind == :closed ||
right_kind == :open ||
right_kind == :unbounded ||
error("invalid right endpoint kind")
(left_kind == :unbounded && right_kind != :unbounded) ||
(left_kind != :unbounded && right_kind == :unbounded) ||
error("exactly one endpoint must be unbounded")
end
new{T}(left_kind, right_kind, value)
end
Exactly one endpoint has to be :unbounded
, but the value
of the other endpoint is always stored in left
, while right
is left uninitialized. This however complicates further code, for instance the one retrieving a left/right endpoint if it exists:
function left_endpoint(int::Interval{T})::Union{T,Nothing} where {T}
if int.left_kind == :unbounded
nothing
else
int.left
end
end
function right_endpoint(int::Interval{T})::Union{T,Nothing} where {T}
if int.right_kind == :unbounded
nothing
else
if int.left_kind == :unbounded
int.left
else
int.right
end
end
end