NumericRange.contains that always works?

Is it not too difficult to make it so

val range: NumericRange.Inclusive[Long] = Long.MinValue to Long.MaxValue

range.contains(Random.nextLong())

always returns true instead of throwing an Exception?

Is it not too difficult to make it so

val intersect = (Long.MinValue to 1L).intersect(-1L to Long.MaxValue)
intersect.contains(0)

returns true instead of throwing an Exception?

I guess I can write extension methods along the lines of:

  extension (r: NumericRange[Long]) {
    def containsX(value: Long): Boolean = {
      if (value == r.end && !r.isInclusive) false
      else if (r.step > 0) {
        if (value < r.start || value > r.end) false
        else (r.step == 1) || (java.lang.Long.remainderUnsigned(value - r.start, r.step) == 0)
      }
      else {
        if (value < r.end || value > r.start) false
        else (r.step == -1) || (java.lang.Long.remainderUnsigned(r.start - value, -r.step) == 0)
      }
    }
    def intersectX(other: NumericRange[Long]): NumericRange[Long] = {
      val newStart = Math.max(r.start, other.start)
      val newEnd = Math.min(r.end, other.end)
      if (newStart > newEnd) {
        NumericRange(0L, 0L, 1L) // empty range
      } else if (r.step != 1 || other.step != 1) {
        //TODO check if step sizes are compatible and can be coalesced into a new range
        throw new Exception("non 1 step handling not implemented")
      } else {
        NumericRange(newStart, newEnd, 1L)
      }
    }
  }

But it would be nice if the standard library was able to have this behavior instead.

I’m exceeding the remit of the Range concept here aren’t I?

Range is not a set.

The fact that it has an intersect function and a contains function should not motivate more set like capabilities.

The list of issues includes the classic

and

and others.

1 Like

It’s possible; it’s just rather annoying to get all the corner cases correct given what is available on Numeric (and, in particular, what you can and cannot assume).

2 Likes

You are still assigned the ticket from 2010.

I think 2019 was the future year of “Blade Runner” and 2029 was the year they came back from in “Terminator”, so right now we’re between those two futures.

2 Likes

…I…am? :see_no_evil_monkey:

1 Like

The main complaint against Range we hear is that it is sometimes too slow. So anything that makes it slower will not be very popular, I fear.

1 Like

I wonder if the complaint comes from the common pattern:

for i <- 1 to n do ...

I’ve had to replace that with while-do a few times (to avoid boxing, the function argument to foreach, etc.). I sometimes wish a few of these patterns could be detected and compiled into some more direct than the general range + function + foreach…

2 Likes

I always using my custom inline version loop for performance, something like:

    inline def foreach[A](array: Array[A])(inline f: A => Unit): Unit =
        var index = 0
        while index < array.length do
            f(array(index))
            index += 1

    inline def foreachWithIndex[A](array: Array[A])(inline f: (Int, A) => Unit): Unit =
        var index = 0
        while index < array.length do
            f(index, array(index))
            index += 1

    inline def cfor[A](inline start: A, inline cond: A => Boolean, inline step: A => A)
                      (inline body: A => Unit): Unit =
        var a = start
        while cond(a) do
            body(a)
            a = step(a)
// and many other versions
3 Likes

Yes, I tend to use methods much like those for arrays also. The foreach I’ve enriched onto the array, and have ranged versions with smart indexing, and so on (and it can’t be called foreach, of course–it’s use and visit in the without/with index versions you used). Anyway, it’s a very good way to go if you want both convenience and performance.

If many folks have the same needs and end up re-implementing the same helper abstractions, would it be better to have a few of those defined in the standard library instead? It’ll make code more readable.