Strings in Swift 4


(James Froggatt) #1

Could we add subscript labels to the list of options? While keeping the range syntax is appealing, I'm concerned it may cause confusion if the operators are used out of context.

The wording is up for debate, but something like this should be a fair alternative:
items[from: i]
items[upTo: i]

Sorry if this has been covered elsewhere (can't find the answer in this thread), but my first questions on discovering these operators (my source of confusion) would be what happens if I try the following:
let partialRange = 0..< //is this an infinite range?
let x = items[partialRange] //shouldn't this cause an out of bounds error?

------------ Begin Message ------------
Group: gmane.comp.lang.swift.evolution
MsgID: <0A458383-2415-4ED4-AD28-88393A671A34@nondot.org>

Jordan points out that the generalized slicing syntax stomps on '...x'
and 'x...', which would be somewhat obvious candidates for variadic
splatting if that ever becomes a thing. Now, variadics are a much more
esoteric feature and slicing is much more important to day-to-day
programming, so this isn't the end of the world IMO, but it is
something we'd be giving up.

Good point, Jordan.

In my experiments with introducing one-sided operators in Swift 3, I was not able to find a case where you actually wanted to write `c[i...]`. Everything I tried needed to use `c[i..<]` instead. My conclusion was that there was no possible use for postfix `...`; after all, `c[i...]` means `c[i...c.endIndex]`, which means `c[i..<c.index(after: c.endIndex)]`, which violates a precondition on `index(after:)`.

Right, the only sensible semantics for a one sided range with an open end point is that it goes to the end of the collection. I see a few different potential colors to paint this bikeshed with, all of which would have the semantics “c[i..<c.endIndex]”:

1) Provide "c[i...]":
2) Provide "c[i..<]":
3) Provide both "c[i..<]” and "c[i…]":

Since all of these operations would have the same behavior, it comes down to subjective questions:

a) Do we want redundancy? IMO, no, which is why #3 is not very desirable.
b) Which is easier to explain to people? As you say, "i..< is shorthand for i..<endindex” is nice and simple, which leans towards #2.
c) Which is subjectively nicer looking? IMO, #1 is much nicer typographically. The ..< formulation looks like symbol soup, particularly because most folks would not put a space before ].

There is no obvious winner, but to me, I tend to prefer #1. What do other folks think?

If that's the case, you can reserve postfix `...` for future variadics features, while using prefix `...` for these one-sided ranges.

I’m personally not very worried about this, the feature doesn’t exist yet and there are lots of ways to spell it. This is something that could and probably should deserve a more explicit/heavy syntax for clarity.

-Chris

···

On Jan 20, 2017, at 9:39 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 20, 2017, at 2:45 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:
on Fri Jan 20 2017, Joe Groff <swift-evolution@swift.org> wrote:

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

------------- End Message -------------

From James


(Dave Abrahams) #2

Could we add subscript labels to the list of options? While keeping
the range syntax is appealing, I'm concerned it may cause confusion if
the operators are used out of context.

The wording is up for debate, but something like this should be a fair alternative:
items[from: i]
items[upTo: i]

If we were to do that, I'd want to drop range notation altogether and
just require this for slicing:

  items[from: i, upTo: j]

The point here is to create a single unified idiom that can be used
everywhere.

Sorry if this has been covered elsewhere (can't find the answer in
this thread), but my first questions on discovering these operators
(my source of confusion) would be what happens if I try the following:
let partialRange = 0..< //is this an infinite range?

It is not a Range, but a RangeExpression with no upper bound.

let x = items[partialRange] //shouldn't this cause an out of bounds error?

No, the upper bound is filled in by the collection

···

on Sun Jan 22 2017, James Froggatt <swift-evolution@swift.org> wrote:

------------ Begin Message ------------
Group: gmane.comp.lang.swift.evolution
MsgID: <0A458383-2415-4ED4-AD28-88393A671A34@nondot.org>

On Jan 20, 2017, at 9:39 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> > wrote:

On Jan 20, 2017, at 2:45 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

on Fri Jan 20 2017, Joe Groff <swift-evolution@swift.org> wrote:

Jordan points out that the generalized slicing syntax stomps on '...x'
and 'x...', which would be somewhat obvious candidates for variadic
splatting if that ever becomes a thing. Now, variadics are a much more
esoteric feature and slicing is much more important to day-to-day
programming, so this isn't the end of the world IMO, but it is
something we'd be giving up.

Good point, Jordan.

In my experiments with introducing one-sided operators in Swift 3, I
was not able to find a case where you actually wanted to write
`c[i...]`. Everything I tried needed to use `c[i..<]` instead. My
conclusion was that there was no possible use for postfix `...`;
after all, `c[i...]` means `c[i...c.endIndex]`, which means
`c[i..<c.index(after: c.endIndex)]`, which violates a precondition
on `index(after:)`.

Right, the only sensible semantics for a one sided range with an open
end point is that it goes to the end of the collection. I see a few
different potential colors to paint this bikeshed with, all of which
would have the semantics “c[i..<c.endIndex]”:

1) Provide "c[i...]":
2) Provide "c[i..<]":
3) Provide both "c[i..<]” and "c[i…]":

Since all of these operations would have the same behavior, it comes down to subjective questions:

a) Do we want redundancy? IMO, no, which is why #3 is not very desirable.
b) Which is easier to explain to people? As you say, "i..< is shorthand for i..<endindex” is nice
and simple, which leans towards #2.
c) Which is subjectively nicer looking? IMO, #1 is much nicer
typographically. The ..< formulation looks like symbol soup,
particularly because most folks would not put a space before ].

There is no obvious winner, but to me, I tend to prefer #1. What do other folks think?

If that's the case, you can reserve postfix `...` for future variadics features, while using

prefix `...` for these one-sided ranges.

I’m personally not very worried about this, the feature doesn’t
exist yet and there are lots of ways to spell it. This is something
that could and probably should deserve a more explicit/heavy syntax
for clarity.

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

------------- End Message -------------

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

--
-Dave


(Dave Abrahams) #3

Could we add subscript labels to the list of options? While keeping
the range syntax is appealing, I'm concerned it may cause confusion if
the operators are used out of context.

The wording is up for debate, but something like this should be a fair alternative:
items[from: i]
items[upTo: i]

Sorry if this has been covered elsewhere (can't find the answer in
this thread), but my first questions on discovering these operators
(my source of confusion) would be what happens if I try the following:
let partialRange = 0..< //is this an infinite range?

If we implemented that syntax, it would be. The proposal is that
half-open ranges without an upper bound would be illegal, but

  let partialRange = 0...

is just such an infinite range, which is useful in its own right. For
example, let's deprecate enumerated():

   for (i, e) in zip(0..., elements) {
     print("\(i): \(e)")
   }

let x = items[partialRange] //shouldn't this cause an out of bounds error?

Why should that be out-of-bounds? Whether it is out-of-bounds would
depend on what items is. If it's an array, that should be equivalent to

   let x = items[items.startIndex..<items.endIndex]

···

on Sun Jan 22 2017, James Froggatt <swift-evolution@swift.org> wrote:

--
-Dave


(Thorsten Seitz) #4

Could we add subscript labels to the list of options? While keeping the range syntax is appealing, I'm concerned it may cause confusion if the operators are used out of context.

Good point!

The wording is up for debate, but something like this should be a fair alternative:
items[from: i]
items[upTo: i]

For me that's at least as readable as the range syntax.

Sorry if this has been covered elsewhere (can't find the answer in this thread), but my first questions on discovering these operators (my source of confusion) would be what happens if I try the following:
let partialRange = 0..< //is this an infinite range?
let x = items[partialRange] //shouldn't this cause an out of bounds error?

Good point! Probably this shouldn't be allowed, making the literal range syntax with open ends tied into the subscript which is a bit confusing indeed.

-Thorsten

···

Am 23.01.2017 um 02:14 schrieb James Froggatt via swift-evolution <swift-evolution@swift.org>:

------------ Begin Message ------------
Group: gmane.comp.lang.swift.evolution
MsgID: <0A458383-2415-4ED4-AD28-88393A671A34@nondot.org>

On Jan 20, 2017, at 9:39 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 20, 2017, at 2:45 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

on Fri Jan 20 2017, Joe Groff <swift-evolution@swift.org> wrote:

Jordan points out that the generalized slicing syntax stomps on '...x'
and 'x...', which would be somewhat obvious candidates for variadic
splatting if that ever becomes a thing. Now, variadics are a much more
esoteric feature and slicing is much more important to day-to-day
programming, so this isn't the end of the world IMO, but it is
something we'd be giving up.

Good point, Jordan.

In my experiments with introducing one-sided operators in Swift 3, I was not able to find a case where you actually wanted to write `c[i...]`. Everything I tried needed to use `c[i..<]` instead. My conclusion was that there was no possible use for postfix `...`; after all, `c[i...]` means `c[i...c.endIndex]`, which means `c[i..<c.index(after: c.endIndex)]`, which violates a precondition on `index(after:)`.

Right, the only sensible semantics for a one sided range with an open end point is that it goes to the end of the collection. I see a few different potential colors to paint this bikeshed with, all of which would have the semantics “c[i..<c.endIndex]”:

1) Provide "c[i...]":
2) Provide "c[i..<]":
3) Provide both "c[i..<]” and "c[i…]":

Since all of these operations would have the same behavior, it comes down to subjective questions:

a) Do we want redundancy? IMO, no, which is why #3 is not very desirable.
b) Which is easier to explain to people? As you say, "i..< is shorthand for i..<endindex” is nice and simple, which leans towards #2.
c) Which is subjectively nicer looking? IMO, #1 is much nicer typographically. The ..< formulation looks like symbol soup, particularly because most folks would not put a space before ].

There is no obvious winner, but to me, I tend to prefer #1. What do other folks think?

If that's the case, you can reserve postfix `...` for future variadics features, while using prefix `...` for these one-sided ranges.

I’m personally not very worried about this, the feature doesn’t exist yet and there are lots of ways to spell it. This is something that could and probably should deserve a more explicit/heavy syntax for clarity.

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

------------- End Message -------------

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


(Nate Cook) #5

`partialRange` here is an incomplete range, not an infinite one. When you use an incomplete range to subscript a collection, the collection "completes" it by filling in the start or end index as required. You can see more about the details of incomplete ranges in this proposal: https://github.com/apple/swift-evolution/blob/master/proposals/0132-sequence-end-ops.md

One other aspect of incomplete ranges that I haven't seen discussed (though I may have missed it) is how they work inpattern matching. It would be nice to use incomplete ranges in switch statements, so that instead of:

    switch x {
    case let y where y < 0: print("< 0")
    case 0...10: print("0-10")
    case let y where y > 10: print("> 10")
    default: print("wat")
    }

we could write:

    switch x {
    case ..<0: print("< 0")
    case 0...10: print("0-10")
    case 10...: print("> 10")
    default: print("wat")
    }

To me, that implies that we'll want a postfix ... operator to exist, though I agree it's not clear what it should do in a subscript. Are there contexts in which we would want i... and i..< to do different things?

Nate

···

On Jan 30, 2017, at 8:51 AM, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> wrote:

Am 23.01.2017 um 02:14 schrieb James Froggatt via swift-evolution <swift-evolution@swift.org>:

Could we add subscript labels to the list of options? While keeping the range syntax is appealing, I'm concerned it may cause confusion if the operators are used out of context.

Good point!

The wording is up for debate, but something like this should be a fair alternative:
items[from: i]
items[upTo: i]

For me that's at least as readable as the range syntax.

Sorry if this has been covered elsewhere (can't find the answer in this thread), but my first questions on discovering these operators (my source of confusion) would be what happens if I try the following:
let partialRange = 0..< //is this an infinite range?
let x = items[partialRange] //shouldn't this cause an out of bounds error?

Good point! Probably this shouldn't be allowed, making the literal range syntax with open ends tied into the subscript which is a bit confusing indeed.


(Dave Abrahams) #6

Could we add subscript labels to the list of options? While keeping

the range syntax is appealing, I'm concerned it may cause confusion
if the operators are used out of context.

Good point!

The wording is up for debate, but something like this should be a fair alternative:
items[from: i]
items[upTo: i]

For me that's at least as readable as the range syntax.

Sorry if this has been covered elsewhere (can't find the answer in
this thread), but my first questions on discovering these operators
(my source of confusion) would be what happens if I try the
following:
let partialRange = 0..< //is this an infinite range?
let x = items[partialRange] //shouldn't this cause an out of bounds error?

Good point! Probably this shouldn't be allowed, making the literal
range syntax with open ends tied into the subscript which is a bit
confusing indeed.

`partialRange` here is an incomplete range, not an infinite one.
When you use an incomplete range to subscript a collection, the
collection "completes" it by filling in the start or end index as
required. You can see more about the details of incomplete ranges in
this proposal:
https://github.com/apple/swift-evolution/blob/master/proposals/0132-sequence-end-ops.md

One other aspect of incomplete ranges that I haven't seen discussed
(though I may have missed it) is how they work inpattern matching. It
would be nice to use incomplete ranges in switch statements, so that
instead of:

    switch x {
    case let y where y < 0: print("< 0")
    case 0...10: print("0-10")
    case let y where y > 10: print("> 10")
    default: print("wat")
    }

we could write:

    switch x {
    case ..<0: print("< 0")
    case 0...10: print("0-10")
    case 10...: print("> 10")
    default: print("wat")
    }

That's interesting!

To me, that implies that we'll want a postfix ... operator to exist,
though I agree it's not clear what it should do in a subscript. Are
there contexts in which we would want i... and i..< to do different
things?

AFAICT no, so there's no reason to support both.

···

on Mon Jan 30 2017, Nate Cook <swift-evolution@swift.org> wrote:

On Jan 30, 2017, at 8:51 AM, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> wrote:

Am 23.01.2017 um 02:14 schrieb James Froggatt via swift-evolution <swift-evolution@swift.org>:

--
-Dave


#7

Are there contexts in which we would want i... and i..< to do different
things?

I posit that in the context of incomplete ranges, we always want “i...” to
mean “greater than or equal to i”, and we do not want “i..<” to mean
anything.

We may, however, want something like “i<..” to mean “strictly greater than
i”, though I’m not sure how it should be spelled.

Nevin

···

On Mon, Jan 30, 2017 at 12:31 PM, Nate Cook via swift-evolution < swift-evolution@swift.org> wrote:

> On Jan 30, 2017, at 8:51 AM, Thorsten Seitz via swift-evolution < > swift-evolution@swift.org> wrote:
>
>> Am 23.01.2017 um 02:14 schrieb James Froggatt via swift-evolution < > swift-evolution@swift.org>:
>>
>> Could we add subscript labels to the list of options? While keeping the
range syntax is appealing, I'm concerned it may cause confusion if the
operators are used out of context.
>
> Good point!
>
>> The wording is up for debate, but something like this should be a fair
alternative:
>> items[from: i]
>> items[upTo: i]
>
> For me that's at least as readable as the range syntax.
>
>>
>> Sorry if this has been covered elsewhere (can't find the answer in this
thread), but my first questions on discovering these operators (my source
of confusion) would be what happens if I try the following:
>> let partialRange = 0..< //is this an infinite range?
>> let x = items[partialRange] //shouldn't this cause an out of bounds
error?
>
> Good point! Probably this shouldn't be allowed, making the literal range
syntax with open ends tied into the subscript which is a bit confusing
indeed.

`partialRange` here is an incomplete range, not an infinite one. When you
use an incomplete range to subscript a collection, the collection
"completes" it by filling in the start or end index as required. You can
see more about the details of incomplete ranges in this proposal:
https://github.com/apple/swift-evolution/blob/master/
proposals/0132-sequence-end-ops.md

One other aspect of incomplete ranges that I haven't seen discussed
(though I may have missed it) is how they work inpattern matching. It would
be nice to use incomplete ranges in switch statements, so that instead of:

    switch x {
    case let y where y < 0: print("< 0")
    case 0...10: print("0-10")
    case let y where y > 10: print("> 10")
    default: print("wat")
    }

we could write:

    switch x {
    case ..<0: print("< 0")
    case 0...10: print("0-10")
    case 10...: print("> 10")
    default: print("wat")
    }

To me, that implies that we'll want a postfix ... operator to exist,
though I agree it's not clear what it should do in a subscript. Are there
contexts in which we would want i... and i..< to do different things?

Nate

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


(Jaden Geller) #8

It seems to me that `items[0…]` would be equivalent to `items[0…Int.max]` if we’re going to treat `0…` as an “infinite" range, no? Otherwise, we’re either giving subscript of InfiniteRange types special behavior or we’re making subscript ignore past-the-end indices; `”hello”.characters[0…10]` would need to return the same as “hello”.characters[0…4]` to be consistent.

···

On Jan 30, 2017, at 11:35 AM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

Why should that be out-of-bounds? Whether it is out-of-bounds would
depend on what items is. If it's an array, that should be equivalent to

  let x = items[items.startIndex..<items.endIndex]


(Thorsten Seitz) #9

Could we add subscript labels to the list of options? While keeping
the range syntax is appealing, I'm concerned it may cause confusion if
the operators are used out of context.

The wording is up for debate, but something like this should be a fair alternative:
items[from: i]
items[upTo: i]

If we were to do that, I'd want to drop range notation altogether and
just require this for slicing:

items[from: i, upTo: j]

The point here is to create a single unified idiom that can be used
everywhere.

Sorry if this has been covered elsewhere (can't find the answer in
this thread), but my first questions on discovering these operators
(my source of confusion) would be what happens if I try the following:
let partialRange = 0..< //is this an infinite range?

It is not a Range, but a RangeExpression with no upper bound.

let x = items[partialRange] //shouldn't this cause an out of bounds error?

No, the upper bound is filled in by the collection

Ah, that makes sense!

-Thorsten

···

Am 24.01.2017 um 20:28 schrieb Dave Abrahams via swift-evolution <swift-evolution@swift.org>:

on Sun Jan 22 2017, James Froggatt <swift-evolution@swift.org> wrote:

------------ Begin Message ------------
Group: gmane.comp.lang.swift.evolution
MsgID: <0A458383-2415-4ED4-AD28-88393A671A34@nondot.org>

On Jan 20, 2017, at 9:39 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> >>> wrote:

On Jan 20, 2017, at 2:45 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

on Fri Jan 20 2017, Joe Groff <swift-evolution@swift.org> wrote:

Jordan points out that the generalized slicing syntax stomps on '...x'
and 'x...', which would be somewhat obvious candidates for variadic
splatting if that ever becomes a thing. Now, variadics are a much more
esoteric feature and slicing is much more important to day-to-day
programming, so this isn't the end of the world IMO, but it is
something we'd be giving up.

Good point, Jordan.

In my experiments with introducing one-sided operators in Swift 3, I
was not able to find a case where you actually wanted to write
`c[i...]`. Everything I tried needed to use `c[i..<]` instead. My
conclusion was that there was no possible use for postfix `...`;
after all, `c[i...]` means `c[i...c.endIndex]`, which means
`c[i..<c.index(after: c.endIndex)]`, which violates a precondition
on `index(after:)`.

Right, the only sensible semantics for a one sided range with an open
end point is that it goes to the end of the collection. I see a few
different potential colors to paint this bikeshed with, all of which
would have the semantics “c[i..<c.endIndex]”:

1) Provide "c[i...]":
2) Provide "c[i..<]":
3) Provide both "c[i..<]” and "c[i…]":

Since all of these operations would have the same behavior, it comes down to subjective questions:

a) Do we want redundancy? IMO, no, which is why #3 is not very desirable.
b) Which is easier to explain to people? As you say, "i..< is shorthand for i..<endindex” is nice
and simple, which leans towards #2.
c) Which is subjectively nicer looking? IMO, #1 is much nicer
typographically. The ..< formulation looks like symbol soup,
particularly because most folks would not put a space before ].

There is no obvious winner, but to me, I tend to prefer #1. What do other folks think?

If that's the case, you can reserve postfix `...` for future variadics features, while using

prefix `...` for these one-sided ranges.

I’m personally not very worried about this, the feature doesn’t
exist yet and there are lots of ways to spell it. This is something
that could and probably should deserve a more explicit/heavy syntax
for clarity.

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

------------- End Message -------------

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

--
-Dave

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


(Dave Abrahams) #10

What is it they say about “a foolish consistency?” :wink:

More seriously, I think you may be viewing these ranges the wrong way
around.

    0...

is not a range with an upper bound of infinity (which is, after all, not
a number!); it's a range with *no* upper bound. When you use the range
for slicing, the collection substitutes its own upper bound.

HTH,

···

on Mon Jan 30 2017, Jaden Geller <jaden.geller-AT-gmail.com> wrote:

On Jan 30, 2017, at 11:35 AM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

Why should that be out-of-bounds? Whether it is out-of-bounds would
depend on what items is. If it's an array, that should be equivalent to

  let x = items[items.startIndex..<items.endIndex]

It seems to me that `items[0…]` would be equivalent to
`items[0…Int.max]` if we’re going to treat `0…` as an “infinite"
range, no? Otherwise, we’re either giving subscript of InfiniteRange
types special behavior or we’re making subscript ignore past-the-end
indices; `”hello”.characters[0…10]` would need to return the same as
“hello”.characters[0…4]` to be consistent.

--
-Dave


(Jaden Geller) #11

I think that is perfectly reasonable, but then it seems weird to be able to iterate over it (with no upper bound) independently of a collection). It would surprise me if

for x in arr[arr.startIndex…] { print(x) }

yielded different results than

for i in arr.startIndex… { print(arr[i]) } // CRASH

which it does under this model.

···

On Jan 31, 2017, at 11:26 AM, Dave Abrahams <dabrahams@apple.com> wrote:

on Mon Jan 30 2017, Jaden Geller <jaden.geller-AT-gmail.com> wrote:

On Jan 30, 2017, at 11:35 AM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

Why should that be out-of-bounds? Whether it is out-of-bounds would
depend on what items is. If it's an array, that should be equivalent to

let x = items[items.startIndex..<items.endIndex]

It seems to me that `items[0…]` would be equivalent to
`items[0…Int.max]` if we’re going to treat `0…` as an “infinite"
range, no? Otherwise, we’re either giving subscript of InfiniteRange
types special behavior or we’re making subscript ignore past-the-end
indices; `”hello”.characters[0…10]` would need to return the same as
“hello”.characters[0…4]` to be consistent.

What is it they say about “a foolish consistency?” :wink:

More seriously, I think you may be viewing these ranges the wrong way
around.

   0...

is not a range with an upper bound of infinity (which is, after all, not
a number!); it's a range with *no* upper bound. When you use the range
for slicing, the collection substitutes its own upper bound.

HTH,

--
-Dave


(Dave Abrahams) #12

That makes perfect sense to me. The first example visually shows the range
being constrained by the bounds of `arr`, whereas in the second example
it is not.

···

on Tue Jan 31 2017, Jaden Geller <jaden.geller-AT-gmail.com> wrote:

On Jan 30, 2017, at 11:35 AM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

Why should that be out-of-bounds? Whether it is out-of-bounds would
depend on what items is. If it's an array, that should be equivalent to

let x = items[items.startIndex..<items.endIndex]

It seems to me that `items[0…]` would be equivalent to
`items[0…Int.max]` if we’re going to treat `0…` as an “infinite"
range, no? Otherwise, we’re either giving subscript of InfiniteRange
types special behavior or we’re making subscript ignore past-the-end
indices; `”hello”.characters[0…10]` would need to return the same as
“hello”.characters[0…4]` to be consistent.

What is it they say about “a foolish consistency?” :wink:

More seriously, I think you may be viewing these ranges the wrong way
around.

   0...

is not a range with an upper bound of infinity (which is, after all, not
a number!); it's a range with *no* upper bound. When you use the range
for slicing, the collection substitutes its own upper bound.

HTH,

--
-Dave

I think that is perfectly reasonable, but then it seems weird to be
able to iterate over it (with no upper bound) independently of a
collection). It would surprise me if

for x in arr[arr.startIndex…] { print(x) }

yielded different results than

for i in arr.startIndex… { print(arr[i]) } // CRASH

which it does under this model.

--
-Dave


(David Sweeris) #13

(I think this how it works... semantically, anyway) Since the upper bound isn't specified, it's inferred from the context.

In the first case, the context is as an index into an array, so the upper bound is inferred to be the last valid index.

In the second case, there is no context, so it goes to Int.max. Then, after the "wrong" context has been established, you try to index an array with numbers from the too-large range.

Semantically speaking, they're pretty different operations. Why is it surprising that they have different results?

- Dave Sweeris

···

On Jan 31, 2017, at 11:32, Jaden Geller via swift-evolution <swift-evolution@swift.org> wrote:

I think that is perfectly reasonable, but then it seems weird to be able to iterate over it (with no upper bound) independently of a collection). It would surprise me if

for x in arr[arr.startIndex…] { print(x) }

yielded different results than

for i in arr.startIndex… { print(arr[i]) } // CRASH

which it does under this model.


(Xiaodi Wu) #14

I think that is perfectly reasonable, but then it seems weird to be able
to iterate over it (with no upper bound) independently of a collection). It
would surprise me if

for x in arr[arr.startIndex…] { print(x) }

yielded different results than

for i in arr.startIndex… { print(arr[i]) } // CRASH

which it does under this model.

(I *think* this how it works... semantically, anyway) Since the upper
bound isn't specified, it's inferred from the context.

In the first case, the context is as an index into an array, so the upper
bound is inferred to be the last valid index.

In the second case, there is no context, so it goes to Int.max. Then,
*after* the "wrong" context has been established, you try to index an
array with numbers from the too-large range.

Semantically speaking, they're pretty different operations. Why is it
surprising that they have different results?

I must say, I was originally rather fond of `0...` as a spelling, but IMO,
Jaden and others have pointed out a real semantic issue.

A range is, to put it simply, the "stuff" between two end points. A "range
with no upper bound" _has to be_ one that continues forever. The upper
bound _must_ be infinity. What Dave Abrahams has described does not have
the semantics of a range with no upper bound. He's describing a standalone
lower bound with no "stuff." It stands to reason that such a type should
not be a sequence at all. But we already use particular types for upper and
lower bounds that aren't sequences, and they're plain numeric types.
Therefore I'd conclude that `arr[upTo: i]` is the most consistent spelling.
It also yields the sensible result that `arr[from: i][upTo: j] == arr[upTo:
j][from: i] == arr[i..<j]`.

If `0...` is to have the semantics of a range with no upper bound, I would
expect `for i in 0...` to be an infinite loop, equivalent to `for i in
stride(from: 0, through: Int.max, by: 1)` for Int.max+1 iterations and then
trapping. Which is, well, silly. I'm coming around to thinking that `0...`
is a sexy notation for something we don't need and a poor notation for
something that's more cleanly expressed by plain numbers.

- Dave Sweeris

···

On Tue, Jan 31, 2017 at 3:36 PM, David Sweeris via swift-evolution < swift-evolution@swift.org> wrote:

On Jan 31, 2017, at 11:32, Jaden Geller via swift-evolution < > swift-evolution@swift.org> wrote:

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


(David Sweeris) #15

Depends… Swift doesn’t allow partial initializations, and neither the `.endIndex` nor the `.upperBound` properties of a `Range` are optional. From a strictly syntactic PoV, a "Range without an upperBound” can’t exist without getting into undefined behavior territory.

Plus, mathematically speaking, an infinite range would be written "[x, ∞)", with an open upper bracket. If you write “[x, ∞]”, with a closed upper bracket, that’s kind of a meaningless statement. I would argue that if we’re going to represent that “infinite” range, the closest Swift spelling would be “x..<“. That leaves the mathematically undefined notation of “[x, ∞]”, spelled as "x…” in Swift, free to let us have “x…” or “…x” (which by similar reasoning can’t mean "(∞, x]”) return one of these:
enum IncompleteRange<T> {
    case upperValue(T)
    case lowerValue(T)
}
which we could then pass to the subscript function of a collection to create the actual Range like this:
extension Collection {
    subscript(_ ir: IncompleteRange<Index>) -> SubSequence {
        switch ir {
        case .lowerValue(let lower): return self[lower ..< self.endIndex]
        case .upperValue(let upper): return self[self.startIndex ..< upper]
        }
    }
}

- Dave Sweeris

···

On Jan 31, 2017, at 2:04 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Tue, Jan 31, 2017 at 3:36 PM, David Sweeris via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 31, 2017, at 11:32, Jaden Geller via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I think that is perfectly reasonable, but then it seems weird to be able to iterate over it (with no upper bound) independently of a collection). It would surprise me if

for x in arr[arr.startIndex…] { print(x) }

yielded different results than

for i in arr.startIndex… { print(arr[i]) } // CRASH

which it does under this model.

(I think this how it works... semantically, anyway) Since the upper bound isn't specified, it's inferred from the context.

In the first case, the context is as an index into an array, so the upper bound is inferred to be the last valid index.

In the second case, there is no context, so it goes to Int.max. Then, after the "wrong" context has been established, you try to index an array with numbers from the too-large range.

Semantically speaking, they're pretty different operations. Why is it surprising that they have different results?

I must say, I was originally rather fond of `0...` as a spelling, but IMO, Jaden and others have pointed out a real semantic issue.

A range is, to put it simply, the "stuff" between two end points. A "range with no upper bound" _has to be_ one that continues forever. The upper bound _must_ be infinity.


(Matthew Johnson) #16

I think that is perfectly reasonable, but then it seems weird to be able to iterate over it (with no upper bound) independently of a collection). It would surprise me if

for x in arr[arr.startIndex…] { print(x) }

yielded different results than

for i in arr.startIndex… { print(arr[i]) } // CRASH

which it does under this model.

(I think this how it works... semantically, anyway) Since the upper bound isn't specified, it's inferred from the context.

In the first case, the context is as an index into an array, so the upper bound is inferred to be the last valid index.

In the second case, there is no context, so it goes to Int.max. Then, after the "wrong" context has been established, you try to index an array with numbers from the too-large range.

Semantically speaking, they're pretty different operations. Why is it surprising that they have different results?

I must say, I was originally rather fond of `0...` as a spelling, but IMO, Jaden and others have pointed out a real semantic issue.

A range is, to put it simply, the "stuff" between two end points. A "range with no upper bound" _has to be_ one that continues forever.
The upper bound _must_ be infinity. What Dave Abrahams has described does not have the semantics of a range with no upper bound. He's describing a standalone lower bound with no "stuff." It stands to reason that such a type should not be a sequence at all. But we already use particular types for upper and lower bounds that aren't sequences, and they're plain numeric types. Therefore I'd conclude that `arr[upTo: i]` is the most consistent spelling. It also yields the sensible result that `arr[from: i][upTo: j] == arr[upTo: j][from: i] == arr[i..<j]`.

If `0...` is to have the semantics of a range with no upper bound, I would expect `for i in 0...` to be an infinite loop, equivalent to `for i in stride(from: 0, through: Int.max, by: 1)` for Int.max+1 iterations and then trapping. Which is, well, silly. I'm coming around to thinking that `0...` is a sexy notation for something we don't need and a poor notation for something that's more cleanly expressed by plain numbers.

I think it’s fair to say that we get to decide on the semantics of postfix `…`. “a range with no upper bound” is very reasonable, but wouldn’t another reasonable semantics be “all the rest”, meaning that there *is* an upper bound (the greatest possible value).

Under the latter semantics, a `for i in 0…` loop would terminate after reaching Int.max. This is probably not what the user intended and would still crash when used in David’s example, but it’s worth considering.

I’m not sure if you read Ben’s post regarding `enumerated` or not, but he gave the example of `zip(0…, sequence)` as a more general replacement for `enumerated`. IMO, he makes a pretty strong case for this.

···

On Jan 31, 2017, at 4:04 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:
On Tue, Jan 31, 2017 at 3:36 PM, David Sweeris via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
On Jan 31, 2017, at 11:32, Jaden Geller via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

- Dave Sweeris

_______________________________________________
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


(Xiaodi Wu) #17

I understand that you can do this from a technical perspective. But I'm
arguing it's devoid of semantics. That is, it's a spelling to dress up a
number.

What is such an `IncompleteRange<T>` other than a value of type T? It's not
an upper bound or lower bound of anything until it's used to index a
collection. Why have a new type (IncompleteRange<T>), a new set of
operators (prefix and postfix range operators), and these muddied semantics
for something that can be written `subscript(upTo upperBound: Index) ->
SubSequence { ... }`? _That_ has unmistakable semantics and requires no new
syntax.

···

On Tue, Jan 31, 2017 at 5:28 PM, David Sweeris <davesweeris@mac.com> wrote:

On Jan 31, 2017, at 2:04 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Tue, Jan 31, 2017 at 3:36 PM, David Sweeris via swift-evolution <swift- > evolution@swift.org> wrote:

On Jan 31, 2017, at 11:32, Jaden Geller via swift-evolution < >> swift-evolution@swift.org> wrote:

I think that is perfectly reasonable, but then it seems weird to be able
to iterate over it (with no upper bound) independently of a collection). It
would surprise me if

for x in arr[arr.startIndex…] { print(x) }

yielded different results than

for i in arr.startIndex… { print(arr[i]) } // CRASH

which it does under this model.

(I *think* this how it works... semantically, anyway) Since the upper
bound isn't specified, it's inferred from the context.

In the first case, the context is as an index into an array, so the upper
bound is inferred to be the last valid index.

In the second case, there is no context, so it goes to Int.max. Then,
*after* the "wrong" context has been established, you try to index an
array with numbers from the too-large range.

Semantically speaking, they're pretty different operations. Why is it
surprising that they have different results?

I must say, I was originally rather fond of `0...` as a spelling, but IMO,
Jaden and others have pointed out a real semantic issue.

A range is, to put it simply, the "stuff" between two end points. A "range
with no upper bound" _has to be_ one that continues forever. The upper
bound _must_ be infinity.

Depends… Swift doesn’t allow partial initializations, and neither the
`.endIndex` nor the `.upperBound` properties of a `Range` are optional.
From a strictly syntactic PoV, a "Range without an upperBound” can’t exist
without getting into undefined behavior territory.

Plus, mathematically speaking, an infinite range would be written "[x,
∞)", with an open upper bracket. If you write “[x, ∞]”, with a *closed*
upper bracket, that’s kind of a meaningless statement. I would argue that
if we’re going to represent that “infinite” range, the closest Swift
spelling would be “x..<“. That leaves the mathematically undefined notation
of “[x, ∞]”, spelled as "x…” in Swift, free to let us have “x…” or “…x”
(which by similar reasoning can’t mean "(∞, x]”) return one of these:

enum IncompleteRange<T> {
    case upperValue(T)
    case lowerValue(T)
}

which we could then pass to the subscript function of a collection to
create the actual Range like this:

extension Collection {
    subscript(_ ir: IncompleteRange<Index>) -> SubSequence {
        switch ir {
        case .lowerValue(let lower): return self[lower ..< self.endIndex]
        case .upperValue(let upper): return self[self.startIndex ..<
upper]
        }
    }
}


(Brent Royal-Gordon) #18

There's a lot I dislike about `subscript(upTo/through/from:)`:

1. We have not previously been very satisfied with how understandable these labels are—for instance, we fiddled around with them a lot when we were looking at `stride(from:to/through:by:)` in Swift 3, and eventually settled on the originals because we couldn't find anything better. I don't think entrenching them further makes very much sense.

2. The fact that you *can* write `arr[from: i][upTo: j]`, and that this is equivalent to both `arr[upTo: j][from: i]` and `arr[i..<j]`, seems a bit weird. We aren't typically in the habit of providing redundant APIs like this.

3. Neither Stdlib nor the Apple frameworks currently contain *any* labeled subscripts, so this design would be unprecedented in the core language.

4. After a new programmer learns about subscripting with two-sided ranges, removing one of the bounds is a straightforward extension of what they already know. The argument label solution is more ad-hoc.

5. The argument label solution solves the immediate problem, but doesn't give us anything else.

To understand what I mean by #5, consider the implementation. The plan is to introduce a `RangeExpression` protocol:

  protocol RangeExpression {
    associatedtype Bound: Comparable
    func relative<C: Collection(to collection: C) where C.Index == Bound -> Range<Bound>
  }

And then reduce the many manually-generated variants of `subscript(_: Range<Index>)` in `Collection` to just two:

  protocol Collection {
    ...
    subscript(bounds: Range<Index>) -> SubSequence { get }
    ...
  }
  
  extension Collection {
    ...
    subscript<Bounds: RangeExpression>(bounds: Bounds) where Bounds.Bound == Index -> SubSequence {
      return self[bounds.relative(to: self)]
    }
    ...
  }

This design would automatically, source-compatibly, handle several different existing types you can slice with:

* ClosedRange
* CountableRange
* CountableClosedRange

Plus the new types associated with incomplete ranges:

* IncompleteRange
* IncompleteClosedRange

Plus anything else we, or users, might want to add. For instance, I have a prototype built on `RangeExpression` which lets you write things like:

  myString[.startIndex + 1 ..< .endIndex - 1]

This strikes me as a pretty cool thing that some people might want.

Similarly, IncompleteRange and IncompleteClosedRange can most likely be put to other uses. They could easily fill a gap in `switch` statements, which don't have a good way to express open-ended comparisons except with a `where` clause. As some have mentioned, when applied to a `Strideable` type they *could* be treated as infinite sequences, although it's not clear if we really want to do that. And, you know, sometimes you really *do* have a case where one or both bounds of a range may be missing in some cases; incomplete ranges are a built-in, batteries-included way to model that.

To put it simply, slicing with incomplete ranges gives us several valuable tools we can apply to other problems. Labeled subscripts, on the other hand, are just another weird little thing that you have to memorize, and probably won't.

···

On Jan 31, 2017, at 2:04 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

Therefore I'd conclude that `arr[upTo: i]` is the most consistent spelling. It also yields the sensible result that `arr[from: i][upTo: j] == arr[upTo: j][from: i] == arr[i..<j]`.

--
Brent Royal-Gordon
Architechies


(Xiaodi Wu) #19

I think it’s fair to say that we get to decide on the semantics of postfix
`…`. “a range with no upper bound” is very reasonable, but wouldn’t
another reasonable semantics be “all the rest”, meaning that there *is* an
upper bound (the greatest possible value).

"All the rest" is by itself insufficient so far as semantics: all the rest
_of what_? Supposing that our supplied lower bound is an integer, it must
be all the rest of the integers. It cannot be all the rest of whatever,
where whatever might be a collection that you try to subset with `0...`.
(Recall that collections move indices, but indices know nothing about the
collections.) It would be exceeding fuzzy for postfix `...` to mean "all
the rest of whatever I want it to mean"--that, almost tautologically, has
no semantics at all.

Under the latter semantics, a `for i in 0…` loop would terminate after

reaching Int.max. This is probably not what the user intended and would
still crash when used in David’s example, but it’s worth considering.

OK, I'm borderline fine with `0... == 0...Int.max`. It at least provides
some semantics (i.e., we're saying `...` refers to all the rest of the
values representable by the type used for the lower bound) [**]. But
Jaden's point still stands, since it would only be consistent if `for i in
arr[0...]` then traps after `arr.count` just like `for i in
arr[0...Int.max]` would do. Otherwise, we really are fudging the semantics.

[**] It is not perfectly consistent semantically because, as was discussed
in threads about our numeric protocols, our integer types are supposed to
model all integers, not just the ones that happen to be representable. Our
model is imperfect because not all integers fit into finite memory, but
that's a modeling artifact and not intentional semantics. IIUC, it would be
otherwise difficult to give a good accounting of, say, the semantics of
addition if arithmetic overflow were an intentional part of the semantics
and not an artifact.

I’m not sure if you read Ben’s post regarding `enumerated` or not, but he

···

On Tue, Jan 31, 2017 at 5:04 PM, Matthew Johnson <matthew@anandabits.com> wrote:

gave the example of `zip(0…, sequence)` as a more general replacement for
`enumerated`. IMO, he makes a pretty strong case for this.

- Dave Sweeris

_______________________________________________
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


(Matthew Johnson) #20

I think that is perfectly reasonable, but then it seems weird to be able to iterate over it (with no upper bound) independently of a collection). It would surprise me if

for x in arr[arr.startIndex…] { print(x) }

yielded different results than

for i in arr.startIndex… { print(arr[i]) } // CRASH

which it does under this model.

(I think this how it works... semantically, anyway) Since the upper bound isn't specified, it's inferred from the context.

In the first case, the context is as an index into an array, so the upper bound is inferred to be the last valid index.

In the second case, there is no context, so it goes to Int.max. Then, after the "wrong" context has been established, you try to index an array with numbers from the too-large range.

Semantically speaking, they're pretty different operations. Why is it surprising that they have different results?

I must say, I was originally rather fond of `0...` as a spelling, but IMO, Jaden and others have pointed out a real semantic issue.

A range is, to put it simply, the "stuff" between two end points. A "range with no upper bound" _has to be_ one that continues forever. The upper bound _must_ be infinity.

Depends… Swift doesn’t allow partial initializations, and neither the `.endIndex` nor the `.upperBound` properties of a `Range` are optional. From a strictly syntactic PoV, a "Range without an upperBound” can’t exist without getting into undefined behavior territory.

Plus, mathematically speaking, an infinite range would be written "[x, ∞)", with an open upper bracket. If you write “[x, ∞]”, with a closed upper bracket, that’s kind of a meaningless statement. I would argue that if we’re going to represent that “infinite” range, the closest Swift spelling would be “x..<“. That leaves the mathematically undefined notation of “[x, ∞]”, spelled as "x…” in Swift, free to let us have “x…” or “…x” (which by similar reasoning can’t mean "(∞, x]”) return one of these:
enum IncompleteRange<T> {
    case upperValue(T)
    case lowerValue(T)
}
which we could then pass to the subscript function of a collection to create the actual Range like this:
extension Collection {
    subscript(_ ir: IncompleteRange<Index>) -> SubSequence {
        switch ir {
        case .lowerValue(let lower): return self[lower ..< self.endIndex]
        case .upperValue(let upper): return self[self.startIndex ..< upper]
        }
    }
}

I understand that you can do this from a technical perspective. But I'm arguing it's devoid of semantics. That is, it's a spelling to dress up a number.

It’s not any more devoid of semantics than a partially applied function. It is a number or index with added semantics that it provides a lower (or upper) bound on the possible value specified by its type.

What is such an `IncompleteRange<T>` other than a value of type T? It's not an upper bound or lower bound of anything until it's used to index a collection. Why have a new type (IncompleteRange<T>), a new set of operators (prefix and postfix range operators), and these muddied semantics for something that can be written `subscript(upTo upperBound: Index) -> SubSequence { ... }`? _That_ has unmistakable semantics and requires no new syntax.

Arguing that it adds too much complexity relative to the value it provides is reasonable. The value in this use case is mostly syntactic sugar so it’s relatively easy to make the case that it doesn’t cary its weight here.

The value in Ben’s use case is a more composable alternative to `enumerated`. I find this to be a reasonably compelling example of the kind of thing a partial range might enable.

I also tend to find concise notation important for clarity as long as it isn’t obscure or idiosyncratic. With that in mind, I think I lean in favor of `…` so long as we’re confident we won’t regret it if / when we take up variadic generics and / or tuple unpacking.

···

On Jan 31, 2017, at 5:35 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:
On Tue, Jan 31, 2017 at 5:28 PM, David Sweeris <davesweeris@mac.com <mailto:davesweeris@mac.com>> wrote:

On Jan 31, 2017, at 2:04 PM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:
On Tue, Jan 31, 2017 at 3:36 PM, David Sweeris via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
On Jan 31, 2017, at 11:32, Jaden Geller via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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