IntervalSets.jl icon indicating copy to clipboard operation
IntervalSets.jl copied to clipboard

Alternative to specify open/closed endpoints

Open andyferris opened this issue 5 years ago • 8 comments

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?

andyferris avatar Sep 06 '18 05:09 andyferris

Exclude doesn’t have any mathematical meaning that I can think of...

dlfivefifty avatar Sep 06 '18 05:09 dlfivefifty

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.

andyferris avatar Sep 06 '18 05:09 andyferris

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)

dlfivefifty avatar Sep 06 '18 05:09 dlfivefifty

PS I’d prefer a swift like syntax a ..< b but that requires parser changes.

dlfivefifty avatar Sep 06 '18 06:09 dlfivefifty

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 ..< b but 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.

andyferris avatar Sep 06 '18 06:09 andyferris

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)

dlfivefifty avatar Sep 06 '18 08:09 dlfivefifty

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.

afbarnard avatar Sep 16 '20 20:09 afbarnard

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))

hyrodium avatar Apr 13 '22 14:04 hyrodium

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...

aplavin avatar Dec 08 '23 23:12 aplavin

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.

hyrodium avatar Dec 09 '23 03:12 hyrodium