Let range operators always return empty ranges if the upper bound is smaller than the lower bound.


(Uwe Falck) #1

I’m looking for feedback on this request if its worth to start an evolution proposal.

Let range operators always return empty ranges if the upper bound is smaller than the lower bound.

···

####

Introduction

####

Consider two loops. The first loop iterator will return an empty range and will not be executed. The second loop throws an error. I would like to see the range iterator always returning an empty range if end index < start index.

for i in 3..<3
{ print(i) }

for i in 3…2
{ print(i) }

####

Motivation

####

The two expressions above are mathematically equivalent and I would like them to return the same result for consistency.

Furthermore, and more important: if C-style for loops are gone with Swift 3.0, programmers may translate

func fibonacci(n: Int) -> Int { // works for n>=0
  var memo = [0,1]
  for var i = 2; i <= n; i++ {
    memo.append(memo[i-1] + memo[i-2])
  }
  return memo[n]
}

probably into

func fibonacci(n: Int) -> Int { // works only for n>=2!
  var memo = [0,1]
  for i in 2...n {
    memo.append(memo[i-1] + memo[i-2])
  }
  return memo[n]
}

This example is from Stackoverflow[1] with two suggested solutions to prevent the runtime error for 0 and 1

let startIndex = 2
let endIndex = n
for i in startIndex.stride(through: endIndex, by: 1) {
  memo.append(memo[i-1] + memo[i-2])
}

…and another one uses the empty range generate by ..<

for i in 2 ..< max(2, n+1) {
  memo.append(memo[i-1] + memo[i-2])
}

Clearly the not-working-solution looks most logical. All other control flow elements, like while, will just not execute if their condition is not met on loop entry.

#####

Proposed solution

#####

Let both range iterators return emtpy ranges, if end index < start index, and not only for a..<b with a==b.

#####

Impact on existing code

#####

None.

####

Alternatives considered

####

If range operators will allow downward variants this idea becomes pointless.

####

Open questions

####

None.

[1] http://stackoverflow.com/questions/34323227/a-concise-way-to-not-execute-a-loop-now-that-c-style-for-loops-are-going-to-be-r?lq=1

Thanks,

--

Uwe


(Félix Cloutier) #2

I don't have a strong opinion on this, but maybe there could be a ..<?/...? operator for ranges that may or may not be well-formed? This solution can be implemented "at home" too.

I'd love to see where and how developers use ranges. It may be more helpful than it looks like. For instance, if you use a range to get an array slice, would your rather have an empty slice if your range underflows instead of the current error behavior?

Félix

···

Le 19 janv. 2016 à 15:46:00, Uwe Falck via swift-evolution <swift-evolution@swift.org> a écrit :

I’m looking for feedback on this request if its worth to start an evolution proposal.

Let range operators always return empty ranges if the upper bound is smaller than the lower bound.

####

Introduction

####

Consider two loops. The first loop iterator will return an empty range and will not be executed. The second loop throws an error. I would like to see the range iterator always returning an empty range if end index < start index.

for i in 3..<3
{ print(i) }

for i in 3…2
{ print(i) }

####

Motivation

####

The two expressions above are mathematically equivalent and I would like them to return the same result for consistency.

Furthermore, and more important: if C-style for loops are gone with Swift 3.0, programmers may translate

func fibonacci(n: Int) -> Int { // works for n>=0
  var memo = [0,1]
  for var i = 2; i <= n; i++ {
    memo.append(memo[i-1] + memo[i-2])
  }
  return memo[n]
}

probably into

func fibonacci(n: Int) -> Int { // works only for n>=2!
  var memo = [0,1]
  for i in 2...n {
    memo.append(memo[i-1] + memo[i-2])
  }
  return memo[n]
}

This example is from Stackoverflow[1] with two suggested solutions to prevent the runtime error for 0 and 1

let startIndex = 2
let endIndex = n
for i in startIndex.stride(through: endIndex, by: 1) {
  memo.append(memo[i-1] + memo[i-2])
}

…and another one uses the empty range generate by ..<

for i in 2 ..< max(2, n+1) {
  memo.append(memo[i-1] + memo[i-2])
}

Clearly the not-working-solution looks most logical. All other control flow elements, like while, will just not execute if their condition is not met on loop entry.

#####

Proposed solution

#####

Let both range iterators return emtpy ranges, if end index < start index, and not only for a..<b with a==b.

#####

Impact on existing code

#####

None.

####

Alternatives considered

####

If range operators will allow downward variants this idea becomes pointless.

####

Open questions

####

None.

[1] http://stackoverflow.com/questions/34323227/a-concise-way-to-not-execute-a-loop-now-that-c-style-for-loops-are-going-to-be-r?lq=1

Thanks,

--

Uwe

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jordan Rose) #3

I'm a minor -1; it feels like this is in the same family as nil-messaging in that it can silently treat invalid input as a no-op. I'm not convinced that "upper bound less than the lower bound" is a strong enough signal for "empty" rather than "logic error". But it seems I'm in the minority.

Jordan

···

On Jan 19, 2016, at 12:46, Uwe Falck via swift-evolution <swift-evolution@swift.org> wrote:

I’m looking for feedback on this request if its worth to start an evolution proposal.

Let range operators always return empty ranges if the upper bound is smaller than the lower bound.


(Howard Lovatt) #4

+1 from me. I have been caught by this. In the degenerate case I didn't
want the loop to be processed, but instead I got a runtime error. I solved
the problem by changing to a C style for loop, but with the removal of C
style for loops this will be more of a problem.

This could be solved with a library function as already suggested, however
having `...`, `..<`, `...?`, and `..<?` seems excessive. Therefore my
suggestion is to make `...` & `..<` behave as proposed (i.e. return empty
ranges when limits mean nothing enclosed by range).

···

On Wednesday, 20 January 2016, Félix Cloutier <swift-evolution@swift.org> wrote:

I don't have a strong opinion on this, but maybe there could be a
..<?/...? operator for ranges that may or may not be well-formed? This
solution can be implemented "at home" too.

I'd love to see where and how developers use ranges. It may be more
helpful than it looks like. For instance, if you use a range to get an
array slice, would your rather have an empty slice if your range underflows
instead of the current error behavior?

Félix

Le 19 janv. 2016 à 15:46:00, Uwe Falck via swift-evolution < > swift-evolution@swift.org > <javascript:_e(%7B%7D,'cvml','swift-evolution@swift.org');>> a écrit :

I’m looking for feedback on this request if its worth to start an
evolution proposal.

Let range operators always return empty ranges if the upper bound is
smaller than the lower bound.

####

Introduction

####

Consider two loops. The first loop iterator will return an empty range and
will not be executed. The second loop throws an error. I would like to see
the range iterator always returning an empty range if end index < start
index.

for i in 3..<3
{ print(i) }

for i in 3…2
{ print(i) }

####

Motivation

####

The two expressions above are mathematically equivalent and I would like
them to return the same result for consistency.

Furthermore, and more important: if C-style for loops are gone with Swift
3.0, programmers may translate

func fibonacci(n: Int) -> Int { // works for
>=0
var memo = [0,1]
for var i = 2; i <= n; i++ {
memo.append(memo[i-1] + memo[i-2])
}
return memo[n]
}

probably into

func fibonacci(n: Int) -> Int { // works only for n>=2!
var memo = [0,1]
for i in 2...n {
memo.append(memo[i-1] + memo[i-2])
}
return memo[n]
}

This example is from Stackoverflow[1] with two suggested solutions to
prevent the runtime error for 0 and 1

let startIndex = 2
let endIndex = n
for i in startIndex.stride(through: endIndex, by: 1) {
memo.append(memo[i-1] + memo[i-2])
}

…and another one uses the empty range generate by ..<

for i in 2 ..< max(2, n+1) {
memo.append(memo[i-1] + memo[i-2])
}

Clearly the not-working-solution looks most logical. All other control
flow elements, like while, will just not execute if their condition is not
met on loop entry.

#####

Proposed solution

#####

Let both range iterators return emtpy ranges, if end index < start index,
and not only for a..<b with a==b.

#####

Impact on existing code

#####

None.

####

Alternatives considered

####

If range operators will allow downward variants this idea becomes
pointless.

####

Open questions

####

None.

[1]
http://stackoverflow.com/questions/34323227/a-concise-way-to-not-execute-a-loop-now-that-c-style-for-loops-are-going-to-be-r?lq=1

Thanks,

--

Uwe

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
<javascript:_e(%7B%7D,'cvml','swift-evolution@swift.org');>
https://lists.swift.org/mailman/listinfo/swift-evolution

--
  -- Howard.


(Dave Abrahams) #5

I'd just like to point out that any proposal for changing the way ranges
work should account for the consequences of the model being explored in
https://github.com/apple/swift/blob/master/test/Prototypes/CollectionsMoveIndices.swift.
We are very much hoping to adopt this model for indexing in the near
future, and it has deep effects on the concept of what an Index is, and
consequently, what a range is, too.

Cheers,
Dave

···

on Tue Jan 19 2016, Uwe Falck <swift-evolution@swift.org> wrote:

I’m looking for feedback on this request if its worth to start an evolution proposal.

Let range operators always return empty ranges if the upper bound is
smaller than the lower bound.


(Ondrej Barina) #6

+1 from me
Ondrej Barina

···

On Wed, Jan 20, 2016 at 2:32 AM, Howard Lovatt via swift-evolution < swift-evolution@swift.org> wrote:

+1 from me. I have been caught by this. In the degenerate case I didn't
want the loop to be processed, but instead I got a runtime error. I solved
the problem by changing to a C style for loop, but with the removal of C
style for loops this will be more of a problem.

This could be solved with a library function as already suggested, however
having `...`, `..<`, `...?`, and `..<?` seems excessive. Therefore my
suggestion is to make `...` & `..<` behave as proposed (i.e. return empty
ranges when limits mean nothing enclosed by range).

On Wednesday, 20 January 2016, Félix Cloutier <swift-evolution@swift.org> > wrote:

I don't have a strong opinion on this, but maybe there could be a
..<?/...? operator for ranges that may or may not be well-formed? This
solution can be implemented "at home" too.

I'd love to see where and how developers use ranges. It may be more
helpful than it looks like. For instance, if you use a range to get an
array slice, would your rather have an empty slice if your range underflows
instead of the current error behavior?

Félix

Le 19 janv. 2016 à 15:46:00, Uwe Falck via swift-evolution < >> swift-evolution@swift.org> a écrit :

I’m looking for feedback on this request if its worth to start an
evolution proposal.

Let range operators always return empty ranges if the upper bound is
smaller than the lower bound.

####

Introduction

####

Consider two loops. The first loop iterator will return an empty range
and will not be executed. The second loop throws an error. I would like to
see the range iterator always returning an empty range if end index < start
index.

for i in 3..<3
{ print(i) }

for i in 3…2
{ print(i) }

####

Motivation

####

The two expressions above are mathematically equivalent and I would like
them to return the same result for consistency.

Furthermore, and more important: if C-style for loops are gone with Swift
3.0, programmers may translate

func fibonacci(n: Int) -> Int { // works for
>=0
var memo = [0,1]
for var i = 2; i <= n; i++ {
memo.append(memo[i-1] + memo[i-2])
}
return memo[n]
}

probably into

func fibonacci(n: Int) -> Int { // works only for n>=2!
var memo = [0,1]
for i in 2...n {
memo.append(memo[i-1] + memo[i-2])
}
return memo[n]
}

This example is from Stackoverflow[1] with two suggested solutions to
prevent the runtime error for 0 and 1

let startIndex = 2
let endIndex = n
for i in startIndex.stride(through: endIndex, by: 1) {
memo.append(memo[i-1] + memo[i-2])
}

…and another one uses the empty range generate by ..<

for i in 2 ..< max(2, n+1) {
memo.append(memo[i-1] + memo[i-2])
}

Clearly the not-working-solution looks most logical. All other control
flow elements, like while, will just not execute if their condition is not
met on loop entry.

#####

Proposed solution

#####

Let both range iterators return emtpy ranges, if end index < start index,
and not only for a..<b with a==b.

#####

Impact on existing code

#####

None.

####

Alternatives considered

####

If range operators will allow downward variants this idea becomes
pointless.

####

Open questions

####

None.

[1]
http://stackoverflow.com/questions/34323227/a-concise-way-to-not-execute-a-loop-now-that-c-style-for-loops-are-going-to-be-r?lq=1

Thanks,

--

Uwe

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
  -- Howard.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Denis Nikitenko) #7

Another hearty +1 from me as well. I’ve run into this recently and used the stride-based work-round. However, using

for i in startIndex.stride(through: endIndex, by: 1){
...
}

instead of

for i in startIndex…endIndex{

}

solely so that the body of the loop never executes if startIndex > endIndex seems unnecessary - especially since

for i in startIndex…<endIndex+1{

}

works perfectly fine. It would make sense to make `…` and `..<` consistent with each other and return an empty range in this case - which would also be consistent with the overall behaviour of stride().

Denis

···

On Jan 20, 2016, at 3:55 AM, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> wrote:

+1

-Thorsten

Am 20.01.2016 um 02:32 schrieb Howard Lovatt via swift-evolution <swift-evolution@swift.org>:

+1 from me. I have been caught by this. In the degenerate case I didn't want the loop to be processed, but instead I got a runtime error. I solved the problem by changing to a C style for loop, but with the removal of C style for loops this will be more of a problem.

This could be solved with a library function as already suggested, however having `...`, `..<`, `...?`, and `..<?` seems excessive. Therefore my suggestion is to make `...` & `..<` behave as proposed (i.e. return empty ranges when limits mean nothing enclosed by range).

On Wednesday, 20 January 2016, Félix Cloutier <swift-evolution@swift.org> wrote:
I don't have a strong opinion on this, but maybe there could be a ..<?/...? operator for ranges that may or may not be well-formed? This solution can be implemented "at home" too.

I'd love to see where and how developers use ranges. It may be more helpful than it looks like. For instance, if you use a range to get an array slice, would your rather have an empty slice if your range underflows instead of the current error behavior?

Félix

Le 19 janv. 2016 à 15:46:00, Uwe Falck via swift-evolution <swift-evolution@swift.org> a écrit :

I’m looking for feedback on this request if its worth to start an evolution proposal.

Let range operators always return empty ranges if the upper bound is smaller than the lower bound.

####

Introduction

####

Consider two loops. The first loop iterator will return an empty range and will not be executed. The second loop throws an error. I would like to see the range iterator always returning an empty range if end index < start index.

for i in 3..<3
{ print(i) }

for i in 3…2
{ print(i) }

####

Motivation

####

The two expressions above are mathematically equivalent and I would like them to return the same result for consistency.

Furthermore, and more important: if C-style for loops are gone with Swift 3.0, programmers may translate

func fibonacci(n: Int) -> Int { // works for n>=0
  var memo = [0,1]
  for var i = 2; i <= n; i++ {
    memo.append(memo[i-1] + memo[i-2])
  }
  return memo[n]
}

probably into

func fibonacci(n: Int) -> Int { // works only for n>=2!
  var memo = [0,1]
  for i in 2...n {
    memo.append(memo[i-1] + memo[i-2])
  }
  return memo[n]
}

This example is from Stackoverflow[1] with two suggested solutions to prevent the runtime error for 0 and 1

let startIndex = 2
let endIndex = n
for i in startIndex.stride(through: endIndex, by: 1) {
  memo.append(memo[i-1] + memo[i-2])
}

…and another one uses the empty range generate by ..<

for i in 2 ..< max(2, n+1) {
  memo.append(memo[i-1] + memo[i-2])
}

Clearly the not-working-solution looks most logical. All other control flow elements, like while, will just not execute if their condition is not met on loop entry.

#####

Proposed solution

#####

Let both range iterators return emtpy ranges, if end index < start index, and not only for a..<b with a==b.

#####

Impact on existing code

#####

None.

####

Alternatives considered

####

If range operators will allow downward variants this idea becomes pointless.

####

Open questions

####

None.

[1] http://stackoverflow.com/questions/34323227/a-concise-way-to-not-execute-a-loop-now-that-c-style-for-loops-are-going-to-be-r?lq=1

Thanks,

--

Uwe

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
  -- Howard.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Thorsten Seitz) #8

+1

-Thorsten

···

Am 20.01.2016 um 02:32 schrieb Howard Lovatt via swift-evolution <swift-evolution@swift.org>:

+1 from me. I have been caught by this. In the degenerate case I didn't want the loop to be processed, but instead I got a runtime error. I solved the problem by changing to a C style for loop, but with the removal of C style for loops this will be more of a problem.

This could be solved with a library function as already suggested, however having `...`, `..<`, `...?`, and `..<?` seems excessive. Therefore my suggestion is to make `...` & `..<` behave as proposed (i.e. return empty ranges when limits mean nothing enclosed by range).

On Wednesday, 20 January 2016, Félix Cloutier <swift-evolution@swift.org> wrote:
I don't have a strong opinion on this, but maybe there could be a ..<?/...? operator for ranges that may or may not be well-formed? This solution can be implemented "at home" too.

I'd love to see where and how developers use ranges. It may be more helpful than it looks like. For instance, if you use a range to get an array slice, would your rather have an empty slice if your range underflows instead of the current error behavior?

Félix

Le 19 janv. 2016 à 15:46:00, Uwe Falck via swift-evolution <swift-evolution@swift.org> a écrit :

I’m looking for feedback on this request if its worth to start an evolution proposal.

Let range operators always return empty ranges if the upper bound is smaller than the lower bound.

####

Introduction

####

Consider two loops. The first loop iterator will return an empty range and will not be executed. The second loop throws an error. I would like to see the range iterator always returning an empty range if end index < start index.

for i in 3..<3
{ print(i) }

for i in 3…2
{ print(i) }

####

Motivation

####

The two expressions above are mathematically equivalent and I would like them to return the same result for consistency.

Furthermore, and more important: if C-style for loops are gone with Swift 3.0, programmers may translate

func fibonacci(n: Int) -> Int { // works for n>=0
  var memo = [0,1]
  for var i = 2; i <= n; i++ {
    memo.append(memo[i-1] + memo[i-2])
  }
  return memo[n]
}

probably into

func fibonacci(n: Int) -> Int { // works only for n>=2!
  var memo = [0,1]
  for i in 2...n {
    memo.append(memo[i-1] + memo[i-2])
  }
  return memo[n]
}

This example is from Stackoverflow[1] with two suggested solutions to prevent the runtime error for 0 and 1

let startIndex = 2
let endIndex = n
for i in startIndex.stride(through: endIndex, by: 1) {
  memo.append(memo[i-1] + memo[i-2])
}

…and another one uses the empty range generate by ..<

for i in 2 ..< max(2, n+1) {
  memo.append(memo[i-1] + memo[i-2])
}

Clearly the not-working-solution looks most logical. All other control flow elements, like while, will just not execute if their condition is not met on loop entry.

#####

Proposed solution

#####

Let both range iterators return emtpy ranges, if end index < start index, and not only for a..<b with a==b.

#####

Impact on existing code

#####

None.

####

Alternatives considered

####

If range operators will allow downward variants this idea becomes pointless.

####

Open questions

####

None.

[1] http://stackoverflow.com/questions/34323227/a-concise-way-to-not-execute-a-loop-now-that-c-style-for-loops-are-going-to-be-r?lq=1

Thanks,

--

Uwe

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
  -- Howard.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Lily Ballard) #9

I'm a minor -1 as well. I think there's value in catching logic errors like this. I'd rather see a ..<? or ...? operator that introduces this behavior.

FWIW, the Range type doesn't actually test this at all. You can say `Range(start: 2, end: 1)` just fine, since Range can be used with elements that aren't Comparable. It's just an overload on ... and ..< for Comparable elements that adds the check.

Also, if we change Range, we'll also need to change ClosedInterval and HalfOpenInterval (both of which include the end >= start check in their initializer, since they maintain an invariant that end >= start, rather than checking in the ... / ..< operators). But unfortunately, this means that the only way to make `2...1` work for ClosedInterval is to relax the invariant that says that end >= start, and I don't feel comfortable doing that. I think that invariant is important to maintain.

The end result of all this is I think we can safely introduce ..<? / ...? operators for Range that require a Comparable element, which make the end of the range equal to the start if it would otherwise be less (because `Range(start: 2, end: 1)` isn't actually empty, instead it contains all values starting at two and incrementing until wraparound hits 1). But the operators wouldn't be able to produce intervals, and wouldn't work for non-comparable elements (the normal ..< / ... operators work just fine on non-comparable elements already). Basically, something like

public func ..< <Pos : ForwardIndexType where Pos : Comparable>(start: Pos, end: Pos) -> Range<Pos> {
  return Range(start: start, end: max(start, end))
}

public func ... <Pos : ForwardIndexType where Pos : Comparable>(start: Pos, end: Pos) -> Range<Pos> {
  let end = max(start, end)
  _precondition(end.successor() > end, "Range end index has no valid successor")
  return Range(start: start, end: end.successor())
}

-Kevin Ballard

···

On Wed, Jan 20, 2016, at 11:42 AM, Jordan Rose via swift-evolution wrote:

I'm a minor -1; it feels like this is in the same family as nil-messaging in that it can silently treat invalid input as a no-op. I'm not convinced that "upper bound less than the lower bound" is a strong enough signal for "empty" rather than "logic error". But it seems I'm in the minority.

Jordan

> On Jan 19, 2016, at 12:46, Uwe Falck via swift-evolution <swift-evolution@swift.org> wrote:
>
> I’m looking for feedback on this request if its worth to start an evolution proposal.
>
>
> Let range operators always return empty ranges if the upper bound is smaller than the lower bound.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Haravikk) #10

I’m inclined to agree though I’m pretty uncertain all round.

To me it seems like the only cases where someone seems likely to end up with an end index lower than their start index is either from input (in which case the value should be tested first), or some kind of logic error, in which case silently failing isn’t going to help resolve that issue (or detect it).

Personally I’d much prefer the opposite solution, which would be to have stride throw an error if the range given is invalid; this would still mean both cases are consistent, but without the risk of silent failures ruining your program.

- Haravikk

···

On 20 Jan 2016, at 19:42, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

I'm a minor -1; it feels like this is in the same family as nil-messaging in that it can silently treat invalid input as a no-op. I'm not convinced that "upper bound less than the lower bound" is a strong enough signal for "empty" rather than "logic error". But it seems I'm in the minority.

Jordan

On Jan 19, 2016, at 12:46, Uwe Falck via swift-evolution <swift-evolution@swift.org> wrote:

I’m looking for feedback on this request if its worth to start an evolution proposal.

Let range operators always return empty ranges if the upper bound is smaller than the lower bound.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jordan Rose) #11

Mm. The reason '..<' doesn't have this behavior, though, is because you get the useful property that "collection[collection.startIndex..<collection.endIndex]" contains the same elements as "collection" itself. It's definitely useful to be able to represent an empty slice at a particular position (say, for replacing with another slice). It's a lot rarer to use '...' for slicing.

Jordan

···

On Jan 20, 2016, at 13:17 , Haravikk <me@haravikk.com> wrote:

I’m inclined to agree though I’m pretty uncertain all round.

To me it seems like the only cases where someone seems likely to end up with an end index lower than their start index is either from input (in which case the value should be tested first), or some kind of logic error, in which case silently failing isn’t going to help resolve that issue (or detect it).

Personally I’d much prefer the opposite solution, which would be to have stride throw an error if the range given is invalid; this would still mean both cases are consistent, but without the risk of silent failures ruining your program.

- Haravikk

On 20 Jan 2016, at 19:42, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

I'm a minor -1; it feels like this is in the same family as nil-messaging in that it can silently treat invalid input as a no-op. I'm not convinced that "upper bound less than the lower bound" is a strong enough signal for "empty" rather than "logic error". But it seems I'm in the minority.

Jordan

On Jan 19, 2016, at 12:46, Uwe Falck via swift-evolution <swift-evolution@swift.org> wrote:

I’m looking for feedback on this request if its worth to start an evolution proposal.

Let range operators always return empty ranges if the upper bound is smaller than the lower bound.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Andrew Bennett) #12

I agree with Kevin/Jordan, I also expect this to always be true:

    Range<Int>(start: a, end: b).startIndex == a
    Range<Int>(start: a, end: b).endIndex == b

Possibly you could adjust Range to be something like this:

    struct Range<T: ForwardIndexType> {

        var start: T, end: T

    }

Ensuring that this is true:
    Range<Int>(start: a, end: b).start == a
    Range<Int>(start: a, end: b).end == b

Then startIndex and endIndex are simply for CollectionType conformance.

    extension Range: CollectionType {

        var startIndex: T { return start }
        var endIndex: T { return start < end ? end : start }
    }

You can still test the validity of the range:
    assert(range.start < range.end)

···

On Thu, Jan 21, 2016 at 8:23 AM, Jordan Rose via swift-evolution < swift-evolution@swift.org> wrote:

Mm. The reason '..<' doesn't have this behavior, though, is because you
get the useful property that
"collection[collection.startIndex..<collection.endIndex]" contains the same
elements as "collection" itself. It's definitely useful to be able to
represent an empty slice at a particular position (say, for replacing with
another slice). It's a lot rarer to use '...' for slicing.

Jordan

> On Jan 20, 2016, at 13:17 , Haravikk <me@haravikk.com> wrote:
>
> I’m inclined to agree though I’m pretty uncertain all round.
>
> To me it seems like the only cases where someone seems likely to end up
with an end index lower than their start index is either from input (in
which case the value should be tested first), or some kind of logic error,
in which case silently failing isn’t going to help resolve that issue (or
detect it).
>
> Personally I’d much prefer the opposite solution, which would be to have
stride throw an error if the range given is invalid; this would still mean
both cases are consistent, but without the risk of silent failures ruining
your program.
>
> - Haravikk
>
>> On 20 Jan 2016, at 19:42, Jordan Rose via swift-evolution < > swift-evolution@swift.org> wrote:
>>
>> I'm a minor -1; it feels like this is in the same family as
nil-messaging in that it can silently treat invalid input as a no-op. I'm
not convinced that "upper bound less than the lower bound" is a strong
enough signal for "empty" rather than "logic error". But it seems I'm in
the minority.
>>
>> Jordan
>>
>>
>>> On Jan 19, 2016, at 12:46, Uwe Falck via swift-evolution < > swift-evolution@swift.org> wrote:
>>>
>>> I’m looking for feedback on this request if its worth to start an
evolution proposal.
>>>
>>>
>>> Let range operators always return empty ranges if the upper bound is
smaller than the lower bound.
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution@swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Haravikk) #13

The tricky thing with ranges at the moment is that they can’t actually represent a full range of an integer type, for example a Range<UInt8> can only represent a range from 0 to 254 (endIndex is 255 but is exclusive). This actually means that a collection type that used a UInt8 for indexes is limited to 255 entries, not 256 as you would expect, which is unfortunate if the collection type allocates storage in powers of two.

I think this means that if ranges were changed to have an inclusive end index then collections would need to be modified to function on the same principal, otherwise we could end up with a range that represents an upper limit that collections cannot, or alternatively we have to artificially limit the supported values for .end, i.e- for a UInt8 .end would be limited to a value of 254, so that .endIndex can be set to 255.

For use with collections I’d love to use inclusive rather than exclusive end indexes for this reason, as while 1 inaccessible element doesn’t seem like much, it still annoys me an unreasonable amount. But would this change the meaning of a range? i.e- 1 ..< 4 isn’t necessarily the same as 1 … 3, it’s only functionally equivalent when dealing with integers since there are no stages in between.

···

On 20 Jan 2016, at 23:19, Andrew Bennett via swift-evolution <swift-evolution@swift.org> wrote:

I agree with Kevin/Jordan, I also expect this to always be true:

    Range<Int>(start: a, end: b).startIndex == a
    Range<Int>(start: a, end: b).endIndex == b

Possibly you could adjust Range to be something like this:

    struct Range<T: ForwardIndexType> {
        var start: T, end: T
    }

Ensuring that this is true:
    Range<Int>(start: a, end: b).start == a
    Range<Int>(start: a, end: b).end == b

Then startIndex and endIndex are simply for CollectionType conformance.

    extension Range: CollectionType {
        var startIndex: T { return start }
        var endIndex: T { return start < end ? end : start }
    }

You can still test the validity of the range:
    assert(range.start < range.end)

On Thu, Jan 21, 2016 at 8:23 AM, Jordan Rose via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Mm. The reason '..<' doesn't have this behavior, though, is because you get the useful property that "collection[collection.startIndex..<collection.endIndex]" contains the same elements as "collection" itself. It's definitely useful to be able to represent an empty slice at a particular position (say, for replacing with another slice). It's a lot rarer to use '...' for slicing.

Jordan

> On Jan 20, 2016, at 13:17 , Haravikk <me@haravikk.com <mailto:me@haravikk.com>> wrote:
>
> I’m inclined to agree though I’m pretty uncertain all round.
>
> To me it seems like the only cases where someone seems likely to end up with an end index lower than their start index is either from input (in which case the value should be tested first), or some kind of logic error, in which case silently failing isn’t going to help resolve that issue (or detect it).
>
> Personally I’d much prefer the opposite solution, which would be to have stride throw an error if the range given is invalid; this would still mean both cases are consistent, but without the risk of silent failures ruining your program.
>
> - Haravikk
>
>> On 20 Jan 2016, at 19:42, Jordan Rose via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>>
>> I'm a minor -1; it feels like this is in the same family as nil-messaging in that it can silently treat invalid input as a no-op. I'm not convinced that "upper bound less than the lower bound" is a strong enough signal for "empty" rather than "logic error". But it seems I'm in the minority.
>>
>> Jordan
>>
>>
>>> On Jan 19, 2016, at 12:46, Uwe Falck via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>>>
>>> I’m looking for feedback on this request if its worth to start an evolution proposal.
>>>
>>>
>>> Let range operators always return empty ranges if the upper bound is smaller than the lower bound.
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Dave Abrahams) #14

FWIW, I'm interested in addressing this issue for Swift 3 in conjunction
with the new indexing model that Dmitri has been developing in
https://github.com/apple/swift/blob/master/test/Prototypes/CollectionsMoveIndices.swift

Cheers,
Dave

···

on Thu Jan 21 2016, Haravikk <swift-evolution@swift.org> wrote:

The tricky thing with ranges at the moment is that they can’t actually
represent a full range of an integer type, for example a Range<UInt8>
can only represent a range from 0 to 254 (endIndex is 255 but is
exclusive).