Alternative to specify open/closed endpoints
In AcceleratedArrays.jl I've been playing with "search intervals" for querying data, for example finding all the dates within a given range with the help of a sort-based acceleration index.
To accommodate open and closed intervals, I've been toying with the idea of syntax that looks like a..exclude(b), which would be closed on the lower bound and open on the upper bound. exclude(b) creates an Exclude object which slightly redefines isequal and isless. See this file, particularly near the bottom.
Basically, I wonder if we've got our bananas turned inside out and whether it would be simpler/more composible to make this a property of the elements (endpoints) of the interval rather than of the interval itself?
Exclude doesn’t have any mathematical meaning that I can think of...
Right... I was thinking that you technically may need something like infinitesimally_larger_than(start) .. infinitesimally_smaller_than(stop). These define the previous (or next) value acording to the (isequal, isless) total ordering. (They don't have to be the same Julia type as start or stop).
I guess it was my feeling that such an interface might be simpler for users and simpler in implementation - it's just the composition of a small number of very simple concepts, rather than having a variety of different interval types and a larger set of interval-reflection functions, etc.
The simplest implementation is always the one that’s already been done...
I can see some benefits of your suggestion, but implementation wise it would require having 3 templates variables: the left endpoint type, the right endpoint type, and the domain type. This might turn into a headache.
If it’s the user facing syntax that’s wanted, that’s easy to add:
..(a::Exclude, b::Exclude) = OpenInterval(a,b)
PS I’d prefer a swift like syntax a ..< b but that requires parser changes.
I'm not currently convinced you need a domain type. You can simply trust isless and isequal to sort out their own promotions for in, etc.
If it’s the user facing syntax that’s wanted, that’s easy to add
Right, that's true!
PS I’d prefer a swift like syntax
a ..< bbut that requires parser changes.
Something like this would be pretty sweet. Alternatively, a unary op for "infinitesimally smaller than" could do a similar thing and be visually similar, I guess.
In SingularIntegralEquations.jl I use the following 1⁻/1⁺ and (x)⁻/(x)⁺. This is possible using something like:
struct Exclude{S,T}
x::T
end
Exclude{S}(x::T) where {S,T} = Exclude{S,T}(x)
const ⁺ = Exclude{true}(true)
const ⁻ = Exclude{false}(true)
*(a::Number, b::Exclude{S}) where S = Exclude{S}(a*b.x)
Here's a Haskell package that puts the open- / closed-ness into the bounds. It also has syntactical sugar for creating intervals similar to that suggested above: a <..< b, a <=..< b, a <..<= b, a <=..<= b. It could be a good reference for design / API questions like this.
How about using macro?
-
@open 1..2(Interval{:open,:open}(1,2)) -
@leftopen 1..2(Interval{:open,:closed}(1,2)) -
@rightopen 1..2(Interval{:closed,:open}(1,2))
Existing syntax that could easily be used:
julia> a .. <(b)
julia> a .. ≤(b)
julia> >(a) .. <(b)
Looks quite nice for the right endpoint, the left one could've been better...
From @masonprotter on slack:
julia> using IntervalSets
julia> macro range_str(s)
for (reg, f) ∈ [r"^\[.*\)$" => Interval{:closed, :open},
r"^\(.*\)$" => Interval{:open, :open},
r"^\(.*\]$" => Interval{:open, :closed},
r"^\[.*\]$" => Interval{:closed, :closed}]
m = match(reg, s)
if !isnothing(m)
args = Meta.parse(s[nextind(s, 1):prevind(s, lastindex(s))])
return :($f($(esc(args))...))
end
end
error("unrecognized expresson $s")
end;
julia> let a = 2
range"[1,a)"
end
1 .. 2 (closed-open)
https://julialang.slack.com/archives/C680MM7D4/p1701897935053029?thread_ts=1701897085.625229&cid=C680MM7D4
I think range"[1,2]" should be interval"[1,2]" (or iv"[1,2]" for short) because the returned value is not an AbstractRange, but using string macro is a great idea.