[Pitch] Adding safety to arrays


(Andrew Hart) #1

Recently I’ve been considering the lack of safety around array indexes.
Swift is designed with safety in mind, so this example would not compile:

var myString: String? = “hello”
myString.append(“ world!”)

The string is optional, not guaranteed to exist, so the last line requires
a “!” to force-unwrap it.

    public func tableView(_ tableView: UITableView, numberOfRowsInSection
section: Int) -> Int {
        let section = self.sections[section]

        return section.items.count
    }

In this example, we could provide a section number that goes beyond the
bounds of the self.sections array, without any warning.

My suggestion is perhaps arrays should by default return an optional when
given an index, and of course they’d support forced-unwrapping too. So you
could then do this:

    let section = self.sections[section]
    if section == nil {
        return 0
    } else {
        return section!.items.count
    }

Or you could do this:

    let section = self.sections[section]!

    return section.items.count

Of course this would be less convenient in a lot of cases, but this is the
1 place where apps seem to encounter a crash, crashing for the same reason
that’s especially avoided across most of the rest of Swift.


Add accessor with bounds check to Array
(David Sweeris) #2

My understanding is that we need the current behavior to meet performance goals. We’ve discussed adding a “safe” subscript before, but the discussion usually fizzles out when no clear winner for the argument label emerges.

- Dave Sweeris

···

On Apr 13, 2017, at 3:56 AM, Andrew Hart via swift-evolution <swift-evolution@swift.org> wrote:

Recently I’ve been considering the lack of safety around array indexes. Swift is designed with safety in mind, so this example would not compile:

var myString: String? = “hello”
myString.append(“ world!”)

The string is optional, not guaranteed to exist, so the last line requires a “!” to force-unwrap it.

    public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let section = self.sections[section]
        
        return section.items.count
    }

In this example, we could provide a section number that goes beyond the bounds of the self.sections array, without any warning.

My suggestion is perhaps arrays should by default return an optional when given an index, and of course they’d support forced-unwrapping too. So you could then do this:

    let section = self.sections[section]
    if section == nil {
        return 0
    } else {
        return section!.items.count
    }

Or you could do this:

    let section = self.sections[section]!
    
    return section.items.count

Of course this would be less convenient in a lot of cases, but this is the 1 place where apps seem to encounter a crash, crashing for the same reason that’s especially avoided across most of the rest of Swift.


(Jeff Kelley) #3

Apologies if this has been suggested before, but going off of Andrew’s message, a simple syntax could be to use our existing Optional syntax:

let array = ["foo", "bar", "baz"]

let first = array[0] // type is `String`
let third = array[2?] // type is `String?`, value is .some("baz")
let fourth = array[3?] // type is `String?`, value is .none

Jeff Kelley

SlaunchaMan@gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan> | jeffkelley.org <http://jeffkelley.org/>

···

On Apr 13, 2017, at 8:19 AM, David Sweeris via swift-evolution <swift-evolution@swift.org> wrote:

On Apr 13, 2017, at 3:56 AM, Andrew Hart via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Recently I’ve been considering the lack of safety around array indexes. Swift is designed with safety in mind, so this example would not compile:

var myString: String? = “hello”
myString.append(“ world!”)

The string is optional, not guaranteed to exist, so the last line requires a “!” to force-unwrap it.

    public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let section = self.sections[section]
        
        return section.items.count
    }

In this example, we could provide a section number that goes beyond the bounds of the self.sections array, without any warning.

My suggestion is perhaps arrays should by default return an optional when given an index, and of course they’d support forced-unwrapping too. So you could then do this:

    let section = self.sections[section]
    if section == nil {
        return 0
    } else {
        return section!.items.count
    }

Or you could do this:

    let section = self.sections[section]!
    
    return section.items.count

Of course this would be less convenient in a lot of cases, but this is the 1 place where apps seem to encounter a crash, crashing for the same reason that’s especially avoided across most of the rest of Swift.

My understanding is that we need the current behavior to meet performance goals. We’ve discussed adding a “safe” subscript before, but the discussion usually fizzles out when no clear winner for the argument label emerges.

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


(Bas Thomas Broek) #4

This has been proposed before. The current implementation is intentional, as seen on the swift-evolution repo: https://github.com/apple/swift-evolution/blob/master/commonly_proposed.md#strings-characters-and-collection-types. This is why: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151214/002446.html

···

Recently I’ve been considering the lack of safety around array indexes. Swift is designed with safety in mind, so this example would not compile:

var myString: String? = “hello”
myString.append(“ world!”)

The string is optional, not guaranteed to exist, so the last line requires a “!” to force-unwrap it.

public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) ->Int {
let section = self.sections[section]

return section.items.count
}

In this example, we could provide a section number that goes beyond the bounds of the self.sections array, without any warning.

My suggestion is perhaps arrays should by default return an optional when given an index, and of course they’d support forced-unwrapping too. So you could then do this:

let section = self.sections[section]
if section == nil {
return 0
} else {
return section!.items.count
}

Or you could do this:

let section = self.sections[section]!

return section.items.count

Of course this would be less convenient in a lot of cases, but this is the 1 place where apps seem to encounter a crash, crashing for the same reason that’s especially avoided across most of the rest of Swift._______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Douglas Gregor) #5

Also, this discussion is common enough that it’s on the “commonly rejected” list:

  https://github.com/apple/swift-evolution/blob/master/commonly_proposed.md

  - Doug

···

On Apr 13, 2017, at 5:19 AM, David Sweeris via swift-evolution <swift-evolution@swift.org> wrote:

On Apr 13, 2017, at 3:56 AM, Andrew Hart via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Recently I’ve been considering the lack of safety around array indexes. Swift is designed with safety in mind, so this example would not compile:

var myString: String? = “hello”
myString.append(“ world!”)

The string is optional, not guaranteed to exist, so the last line requires a “!” to force-unwrap it.

    public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let section = self.sections[section]
        
        return section.items.count
    }

In this example, we could provide a section number that goes beyond the bounds of the self.sections array, without any warning.

My suggestion is perhaps arrays should by default return an optional when given an index, and of course they’d support forced-unwrapping too. So you could then do this:

    let section = self.sections[section]
    if section == nil {
        return 0
    } else {
        return section!.items.count
    }

Or you could do this:

    let section = self.sections[section]!
    
    return section.items.count

Of course this would be less convenient in a lot of cases, but this is the 1 place where apps seem to encounter a crash, crashing for the same reason that’s especially avoided across most of the rest of Swift.

My understanding is that we need the current behavior to meet performance goals. We’ve discussed adding a “safe” subscript before, but the discussion usually fizzles out when no clear winner for the argument label emerges.


(Dave Abrahams) #6

Also, please don't use the word “safe” this way around here as it
conflicts with the definition used by Swift. As defined by Swift,
safety has nothing to do with whether something might trap or whether
it's spelled with a "!", but about whether it can corrupt memory, and by
that measure, Array (aside from withUnsafe[Mutable]BufferPointer and
passing it to functions taking Unsafe[Mutable]Pointer) is totally safe.

···

on Thu Apr 13 2017, David Sweeris <swift-evolution@swift.org> wrote:

On Apr 13, 2017, at 3:56 AM, Andrew Hart via swift-evolution > <swift-evolution@swift.org> wrote:

Recently I’ve been considering the lack of safety around array indexes. Swift is designed with

safety in mind, so this example would not compile:

var myString: String? = “hello”

myString.append(“ world!”)

The string is optional, not guaranteed to exist, so the last line requires a “!” to force-unwrap it.

    public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let section = self.sections[section]
        
        return section.items.count
    }

In this example, we could provide a section number that goes beyond the bounds of the self.sections array, without any warning.

My suggestion is perhaps arrays should by default return an optional when given an index, and of course they’d support forced-unwrapping too. So you could then do this:

    let section = self.sections[section]
    if section == nil {
        return 0
    } else {
        return section!.items.count
    }

Or you could do this:

    let section = self.sections[section]!
    
    return section.items.count

Of course this would be less convenient in a lot of cases, but this is the 1 place where apps seem

to encounter a crash, crashing for the same reason that’s especially avoided across most of the rest
of Swift.

My understanding is that we need the current behavior to meet
performance goals. We’ve discussed adding a “safe” subscript before,
but the discussion usually fizzles out when no clear winner for the
argument label emerges.

--
-Dave


(David Sweeris) #7

That was my thought, too, but it's not valid Swift. I tried it in a playground before I sent my reply. At least in Xcode 8.3.1, it gives an error about ambiguous calls (it can't decide if you're trying to call the stdlib subscript or the `(_: Index?) -> Element?` one from the extension), and if you fix that by adding an argument label, the real issue is revealed which is that saying "x?" doesn't return "x wrapped in an optional"; it thinks you're trying to do Optional chaining on a non-optional value, which isn't allowed.

I wouldn't object to adding that "trailing '?' wraps the value" behavior (or maybe "¿", since they really would have the opposite result), in which case this should work fine (although the choice of argument label, or lack thereof, will likely still be bikeshedded)

Anyway, unless someone who knows more about this than I do (probably most of you) says this functionality is a Bad Idea (™), I'm in favor of it.

- Dave Sweeris

···

Sent from my iPhone

On Apr 13, 2017, at 07:34, Jeff Kelley <slaunchaman@gmail.com> wrote:

Apologies if this has been suggested before, but going off of Andrew’s message, a simple syntax could be to use our existing Optional syntax:

let array = ["foo", "bar", "baz"]

let first = array[0] // type is `String`
let third = array[2?] // type is `String?`, value is .some("baz")
let fourth = array[3?] // type is `String?`, value is .none


(Josh Parmenter) #8

This seems inconsistent to me. 2 is 2... 2 itself is not optional. You wouldn't expect 2 to be unwrapped.
Best
Josh

···

Sent from my iPhone

On Apr 13, 2017, at 08:48, Jeff Kelley via swift-evolution <swift-evolution@swift.org<mailto:swift-evolution@swift.org>> wrote:

Apologies if this has been suggested before, but going off of Andrew's message, a simple syntax could be to use our existing Optional syntax:

let array = ["foo", "bar", "baz"]

let first = array[0] // type is `String`
let third = array[2?] // type is `String?`, value is .some("baz")
let fourth = array[3?] // type is `String?`, value is .none

Jeff Kelley

SlaunchaMan@gmail.com<mailto:SlaunchaMan@gmail.com> | @SlaunchaMan<https://twitter.com/SlaunchaMan> | jeffkelley.org<http://jeffkelley.org>

On Apr 13, 2017, at 8:19 AM, David Sweeris via swift-evolution <swift-evolution@swift.org<mailto:swift-evolution@swift.org>> wrote:

On Apr 13, 2017, at 3:56 AM, Andrew Hart via swift-evolution <swift-evolution@swift.org<mailto:swift-evolution@swift.org>> wrote:

Recently I've been considering the lack of safety around array indexes. Swift is designed with safety in mind, so this example would not compile:

var myString: String? = "hello"
myString.append(" world!")

The string is optional, not guaranteed to exist, so the last line requires a "!" to force-unwrap it.

    public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let section = self.sections[section]

        return section.items.count
    }

In this example, we could provide a section number that goes beyond the bounds of the self.sections array, without any warning.

My suggestion is perhaps arrays should by default return an optional when given an index, and of course they'd support forced-unwrapping too. So you could then do this:

    let section = self.sections[section]
    if section == nil {
        return 0
    } else {
        return section!.items.count
    }

Or you could do this:

    let section = self.sections[section]!

    return section.items.count

Of course this would be less convenient in a lot of cases, but this is the 1 place where apps seem to encounter a crash, crashing for the same reason that's especially avoided across most of the rest of Swift.

My understanding is that we need the current behavior to meet performance goals. We've discussed adding a "safe" subscript before, but the discussion usually fizzles out when no clear winner for the argument label emerges.

- 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<mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Lucas Neiva) #9

AFAIK arrays use the same “subscripts” [1] feature as other collections. So this feature would have to be tagged onto subscripts in general.

I see a few problems with you suggestion:

- How do you differentiate between an out-of-bounds subscript access in an array with optional elements, e.g. `[String?]`, `[Int: String?]`
- Isn’t it conflating two different things, to use the same `?` operator here
- You’d probably have to add a new construct similar to the existing subscripts feature

[1] https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Subscripts.html

···

On 13.04.2017, at 16:34, Jeff Kelley via swift-evolution <swift-evolution@swift.org> wrote:

Apologies if this has been suggested before, but going off of Andrew’s message, a simple syntax could be to use our existing Optional syntax:

let array = ["foo", "bar", "baz"]

let first = array[0] // type is `String`
let third = array[2?] // type is `String?`, value is .some("baz")
let fourth = array[3?] // type is `String?`, value is .none

Jeff Kelley

SlaunchaMan@gmail.com | @SlaunchaMan | jeffkelley.org

On Apr 13, 2017, at 8:19 AM, David Sweeris via swift-evolution <swift-evolution@swift.org> wrote:

On Apr 13, 2017, at 3:56 AM, Andrew Hart via swift-evolution <swift-evolution@swift.org> wrote:

Recently I’ve been considering the lack of safety around array indexes. Swift is designed with safety in mind, so this example would not compile:

var myString: String? = “hello”
myString.append(“ world!”)

The string is optional, not guaranteed to exist, so the last line requires a “!” to force-unwrap it.

    public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let section = self.sections[section]
        
        return section.items.count
    }

In this example, we could provide a section number that goes beyond the bounds of the self.sections array, without any warning.

My suggestion is perhaps arrays should by default return an optional when given an index, and of course they’d support forced-unwrapping too. So you could then do this:

    let section = self.sections[section]
    if section == nil {
        return 0
    } else {
        return section!.items.count
    }

Or you could do this:

    let section = self.sections[section]!
    
    return section.items.count

Of course this would be less convenient in a lot of cases, but this is the 1 place where apps seem to encounter a crash, crashing for the same reason that’s especially avoided across most of the rest of Swift.

My understanding is that we need the current behavior to meet performance goals. We’ve discussed adding a “safe” subscript before, but the discussion usually fizzles out when no clear winner for the argument label emerges.

- 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


(Lucas Neiva) #10

I think most people would agree with “its a Not a Great Idea (™)”.

The root argument is “out-of-bounds array access is a logic error”.

···

On 13.04.2017, at 16:54, David Sweeris via swift-evolution <swift-evolution@swift.org> wrote:

Sent from my iPhone
On Apr 13, 2017, at 07:34, Jeff Kelley <slaunchaman@gmail.com> wrote:

Apologies if this has been suggested before, but going off of Andrew’s message, a simple syntax could be to use our existing Optional syntax:

let array = ["foo", "bar", "baz"]

let first = array[0] // type is `String`
let third = array[2?] // type is `String?`, value is .some("baz")
let fourth = array[3?] // type is `String?`, value is .none

That was my thought, too, but it's not valid Swift. I tried it in a playground before I sent my reply. At least in Xcode 8.3.1, it gives an error about ambiguous calls (it can't decide if you're trying to call the stdlib subscript or the `(_: Index?) -> Element?` one from the extension), and if you fix that by adding an argument label, the real issue is revealed which is that saying "x?" doesn't return "x wrapped in an optional"; it thinks you're trying to do Optional chaining on a non-optional value, which isn't allowed.

I wouldn't object to adding that "trailing '?' wraps the value" behavior (or maybe "¿", since they really would have the opposite result), in which case this should work fine (although the choice of argument label, or lack thereof, will likely still be bikeshedded)

Anyway, unless someone who knows more about this than I do (probably most of you) says this functionality is a Bad Idea (™), I'm in favor of it.

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


(David Sweeris) #11

Correct. I think the idea was that "2?" would get converted to an `Optional<Int>`, which would then call a subscript that took an `Index?` instead of `Index`. The trailing "?" doesn't do that, but for some reason I thought it might.

IMHO, the semantics are clear(ish) in context, but it feels slightly odd for "?" to have two opposite behaviors depending on whether the argument is or isn't an Optional.

In any case, given that we're discussing a "safe subscript", we need a way to differentiate the safe subscript from the unsafe subscript. The only two ways to do that are to either add an argument label or change the argument type, and IMHO the best alternate argument type is `Index?`, since it's pretty light-weight, already part of the stdlib, and already familiar to Swift developers.

Someone already said it was a bad idea, though, so I'm rethinking my support.

- Dave Sweeris

···

On Apr 13, 2017, at 08:53, Josh Parmenter <jparmenter@vectorform.com> wrote:

This seems inconsistent to me. 2 is 2... 2 itself is not optional. You wouldn't expect 2 to be unwrapped.


(Jeff Kelley) #12

Yes, the idea was to key off of an optional index. something like this:

extension Array {
    public subscript(_ index: Int?) -> Element? {
        guard let i = index, i < endIndex else { return nil }
        
        return self[i]
    }
}

let array = ["foo", "bar", "baz"]

array[1] // "bar"

let index: Int? = 5
let notInArray = array[index]

This doesn’t work today with the error “ambiguous use of 'subscript’” on “self[i]” but surely that could be fixed.

Jeff Kelley

SlaunchaMan@gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan> | jeffkelley.org <http://jeffkelley.org/>

···

On Apr 13, 2017, at 12:18 PM, David Sweeris <davesweeris@mac.com> wrote:

On Apr 13, 2017, at 08:53, Josh Parmenter <jparmenter@vectorform.com> wrote:

This seems inconsistent to me. 2 is 2... 2 itself is not optional. You wouldn't expect 2 to be unwrapped.

Correct. I think the idea was that "2?" would get converted to an `Optional<Int>`, which would then call a subscript that took an `Index?` instead of `Index`. The trailing "?" doesn't do that, but for some reason I thought it might.

IMHO, the semantics are clear(ish) in context, but it feels slightly odd for "?" to have two opposite behaviors depending on whether the argument is or isn't an Optional.

In any case, given that we're discussing a "safe subscript", we need a way to differentiate the safe subscript from the unsafe subscript. The only two ways to do that are to either add an argument label or change the argument type, and IMHO the best alternate argument type is `Index?`, since it's pretty light-weight, already part of the stdlib, and already familiar to Swift developers.

Someone already said it was a bad idea, though, so I'm rethinking my support.

- Dave Sweeris


(Brent Royal-Gordon) #13

Compiler and syntax issues aside, the real issue with using `Int?` as the type is this: If you take `Int?` as a subscript type, then someone can say `foos[nil]`. What are you going to do about that? It's obviously incorrect, so you really ought to fail a precondition, but that means you've opened a "safety" hole as bad as the original one, purely to get a nice syntax.

So, I think if you're going to do this, you need an argument label. The question then is, what label should we use?

The most common choice is `safe`, but "safe" has a specific meaning in the Swift compiler and standard library (i.e. memory safety), and we shouldn't muddy that. Instead I'll nominate `checking`, which in context reads like:

  if let foo = foos[checking: i] { … }

(But keep in mind that you shouldn't use the checking subscript in `tableView(_:numberOfRowsInSection:)` or any of the other table view delegate methods. Section and row indices should always be derived from the count of the same array you're indexing, so it should never be possible to receive an invalid section or row index in a table view delegate/data source method. If that happens, the only possible cause is programmer error, and the correct behavior when Swift code detects programmer error is to fail a precondition and trap.

I'd actually say the #1 reason not to add this feature is that a lot of developers don't seem to understand this, and they're likely to use the feature to make their code try to continue in the face of programmer error instead of trapping like it properly should. A program in an inconsistent state is dangerous; best to stop it quickly before it does some damage.)

···

On Apr 13, 2017, at 9:18 AM, David Sweeris via swift-evolution <swift-evolution@swift.org> wrote:

In any case, given that we're discussing a "safe subscript", we need a way to differentiate the safe subscript from the unsafe subscript. The only two ways to do that are to either add an argument label or change the argument type, and IMHO the best alternate argument type is `Index?`, since it's pretty light-weight, already part of the stdlib, and already familiar to Swift developers.

--
Brent Royal-Gordon
Architechies


(Tony Allevato) #14

In any case, given that we're discussing a "safe subscript", we need a way
to differentiate the safe subscript from the unsafe subscript. The only two
ways to do that are to either add an argument label or change the argument
type, and IMHO the best alternate argument type is `Index?`, since it's
pretty light-weight, already part of the stdlib, and already familiar to
Swift developers.

Compiler and syntax issues aside, the real issue with using `Int?` as the
type is this: If you take `Int?` as a subscript type, then someone can say
`foos[nil]`. What are you going to do about that? It's obviously incorrect,
so you really ought to fail a precondition, but that means you've opened a
"safety" hole as bad as the original one, purely to get a nice syntax.

So, I think if you're going to do this, you need an argument label. The
question then is, what label should we use?

The most common choice is `safe`, but "safe" has a specific meaning in the
Swift compiler and standard library (i.e. memory safety), and we shouldn't
muddy that. Instead I'll nominate `checking`, which in context reads like:

if let foo = foos[checking: i] { … }

(But keep in mind that you shouldn't use the checking subscript in
`tableView(_:numberOfRowsInSection:)` or any of the other table view
delegate methods. Section and row indices should always be derived from the
count of the same array you're indexing, so it should never be possible to
receive an invalid section or row index in a table view delegate/data
source method. If that happens, the only possible cause is programmer
error, and the correct behavior when Swift code detects programmer error is
to fail a precondition and trap.

I'd actually say the #1 reason not to add this feature is that a lot of
developers don't seem to understand this, and they're likely to use the
feature to make their code try to continue in the face of programmer error
instead of trapping like it properly should. A program in an inconsistent
state is dangerous; best to stop it quickly before it does some damage.)

Since you already beat me to the rest of what I was typing about optionals
as the index, I'll +1 the sentiment in this last part :slight_smile:

The need for checking subscripts should be *extremely rare*, which is why I
think most of the proposals in this area flounder.

An out-of-bound index is a *logic error* in the program. In most cases
(like the tableView example cited above), the indices you use elsewhere to
access elements of those arrays should already be guaranteed by other
invariants in your program. If they're not, and if you think you need to
use a checked subscript, then you're just putting some tape over the
problem instead of fixing a serious underlying bug.

Most of the time you're working with arrays, you should be using for-in
loops (where indexes don't matter) or the Collection index APIs (which will
guarantee that you're getting valid indexes without worrying about that
fact that they're numbers underneath).

It should be quite rare that you're accessing elements at arbitrary numeric
indexes within an array. I imagine most such cases come from user
input—either a number the user has entered, or them clicking on a
particular location on a view and then you translate that pixel location
into an array index and look something up that way. In those cases, you
should probably just do the bounds checking yourself.

My 2 cents: Giving users checked subscripts for arrays will do more harm
than good for novice programmers—they're going to do the wrong thing with
them.

···

On Thu, Apr 13, 2017 at 2:55 PM Brent Royal-Gordon via swift-evolution < swift-evolution@swift.org> wrote:

On Apr 13, 2017, at 9:18 AM, David Sweeris via swift-evolution < > swift-evolution@swift.org> wrote:

--
Brent Royal-Gordon
Architechies

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


(Karl) #15

I'd actually say the #1 reason not to add this feature is that a lot of developers don't seem to understand this, and they're likely to use the feature to make their code try to continue in the face of programmer error instead of trapping like it properly should. A program in an inconsistent state is dangerous; best to stop it quickly before it does some damage.)

Right, so I think the reason is actually that a lot of developers don’t understand what an Array is. There are two use-cases for an Array:

1) As a string of items, don’t care about the length. The maximum prior knowledge you can have is that the order may or may not be significant. This includes operations like iteration, mapping, reducing and filtering.
2) As a string of items of specific length. You have prior knowledge about what you expect to find at each location. This includes operations like random-access subscripting, which is what we’re talking about.

Basically, the interesting part of a statement such as “let someValue = myArray[2]” is: why index 2? What’s so special about that element; why couldn't someValue be the item at any index N instead? It’s because we know to expect something of special significance at index 2.

In that case, the only time myArray[2] will fail is when your prior knowledge breaks down. The type-system has no way to encode and check for the length of an Array, and that has allowed somebody to pass in a bad value. So what to do?

A) If you absolutely require a value for myArray[2]: Insert a precondition check.
B) If you can still continue without myArray[2]: Check the length of the Array. Your logic will be branching anyway in this case, to account for the value (and subsequent values) being/not being present.

Personally, the only valid use-case I can think of is when you want to initialise an Array’s elements out-of-order - i.e., you want to set a value for myArray[2] despite myArray[0] and [1] not being populated. In that case, it would be better to have some kind of SparseArray type, and for us to have a proper API for unsafe initialisation of stdlib types.

- Karl


(David Sweeris) #16

I'm torn... On the one hand, I see the logic in this. On the other hand, I suspect many developers will end up writing such a function anyway (regardless of whether it's a good idea), and it'd be nice if there was a standard spelling.

I realize this is not a convincing argument; I'm just thinking out loud a bit...

Maybe I'll start a "StdBadIdeasLib" project, for all the ideas that could be implemented straightforwardly on top of the existing stdlib, but get rejected because they're bad ideas... Come to think of it, that's probably a bad idea itself (at least it'd be a well-named library, though).

- Dave Sweeris

···

On Apr 13, 2017, at 15:01, Tony Allevato <tony.allevato@gmail.com> wrote:

My 2 cents: Giving users checked subscripts for arrays will do more harm than good for novice programmers—they're going to do the wrong thing with them.


(Xiaodi Wu) #17

A proposal for lenient subscripts has already been written, and a PR has
been created on the swift-evolution repo and it has been closed for not
being in scope. When it is in scope, it can be considered again. But in the
meantime, the topic has been visited so many times that it's extremely
unlikely for anything previously unsaid to be newly discovered at this
point.

···

On Thu, Apr 13, 2017 at 7:11 PM, David Sweeris via swift-evolution < swift-evolution@swift.org> wrote:

> On Apr 13, 2017, at 15:01, Tony Allevato <tony.allevato@gmail.com> > wrote:
>
> My 2 cents: Giving users checked subscripts for arrays will do more harm
than good for novice programmers—they're going to do the wrong thing with
them.

I'm torn... On the one hand, I see the logic in this. On the other hand, I
suspect many developers will end up writing such a function anyway
(regardless of whether it's a good idea), and it'd be nice if there was a
standard spelling.

I realize this is not a convincing argument; I'm just thinking out loud a
bit...

Maybe I'll start a "StdBadIdeasLib" project, for all the ideas that could
be implemented straightforwardly on top of the existing stdlib, but get
rejected because they're bad ideas... Come to think of it, that's probably
a bad idea itself (at least it'd be a well-named library, though).

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


(Riley Testut) #18

Personally, the only valid use-case I can think of is when you want to initialise an Array’s elements out-of-order - i.e., you want to set a value for myArray[2] despite myArray[0] and [1] not being populated. In that case, it would be better to have some kind of SparseArray type, and for us to have a proper API for unsafe initialisation of stdlib types.

Wouldn't the same functionality be accomplished by a Dictionary with Int as the key type?

···

On Apr 14, 2017, at 10:00 AM, Karl Wagner via swift-evolution <swift-evolution@swift.org> wrote:

I'd actually say the #1 reason not to add this feature is that a lot of developers don't seem to understand this, and they're likely to use the feature to make their code try to continue in the face of programmer error instead of trapping like it properly should. A program in an inconsistent state is dangerous; best to stop it quickly before it does some damage.)

Right, so I think the reason is actually that a lot of developers don’t understand what an Array is. There are two use-cases for an Array:

1) As a string of items, don’t care about the length. The maximum prior knowledge you can have is that the order may or may not be significant. This includes operations like iteration, mapping, reducing and filtering.
2) As a string of items of specific length. You have prior knowledge about what you expect to find at each location. This includes operations like random-access subscripting, which is what we’re talking about.

Basically, the interesting part of a statement such as “let someValue = myArray[2]” is: why index 2? What’s so special about that element; why couldn't someValue be the item at any index N instead? It’s because we know to expect something of special significance at index 2.

In that case, the only time myArray[2] will fail is when your prior knowledge breaks down. The type-system has no way to encode and check for the length of an Array, and that has allowed somebody to pass in a bad value. So what to do?

A) If you absolutely require a value for myArray[2]: Insert a precondition check.
B) If you can still continue without myArray[2]: Check the length of the Array. Your logic will be branching anyway in this case, to account for the value (and subsequent values) being/not being present.

Personally, the only valid use-case I can think of is when you want to initialise an Array’s elements out-of-order - i.e., you want to set a value for myArray[2] despite myArray[0] and [1] not being populated. In that case, it would be better to have some kind of SparseArray type, and for us to have a proper API for unsafe initialisation of stdlib types.

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


(Dave Abrahams) #19

That is a point in the design space, but there's really no need to
expose unsafe initialization in order to get that functionality. You
could simply build ArrayOfOptional<T> that would present the same API as
Array<T?> but would use a separate inline buffer of bits to denote which
of the optionals were non-nil.

···

on Fri Apr 14 2017, Karl Wagner <swift-evolution@swift.org> wrote:

Personally, the only valid use-case I can think of is when you want to
initialise an Array’s elements out-of-order - i.e., you want to set a
value for myArray[2] despite myArray[0] and [1] not being
populated. In that case, it would be better to have some kind of
SparseArray type, and for us to have a proper API for unsafe
initialisation of stdlib types.

--
-Dave


(Saagar Jha) #20

A Dictionary uses a lot more space than an Array, though, and allow for bogus keys like “-1”, etc.

Saagar Jha

···

On Apr 16, 2017, at 10:34, Riley Testut via swift-evolution <swift-evolution@swift.org> wrote:

Personally, the only valid use-case I can think of is when you want to initialise an Array’s elements out-of-order - i.e., you want to set a value for myArray[2] despite myArray[0] and [1] not being populated. In that case, it would be better to have some kind of SparseArray type, and for us to have a proper API for unsafe initialisation of stdlib types.

Wouldn't the same functionality be accomplished by a Dictionary with Int as the key type?

On Apr 14, 2017, at 10:00 AM, Karl Wagner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I'd actually say the #1 reason not to add this feature is that a lot of developers don't seem to understand this, and they're likely to use the feature to make their code try to continue in the face of programmer error instead of trapping like it properly should. A program in an inconsistent state is dangerous; best to stop it quickly before it does some damage.)

Right, so I think the reason is actually that a lot of developers don’t understand what an Array is. There are two use-cases for an Array:

1) As a string of items, don’t care about the length. The maximum prior knowledge you can have is that the order may or may not be significant. This includes operations like iteration, mapping, reducing and filtering.
2) As a string of items of specific length. You have prior knowledge about what you expect to find at each location. This includes operations like random-access subscripting, which is what we’re talking about.

Basically, the interesting part of a statement such as “let someValue = myArray[2]” is: why index 2? What’s so special about that element; why couldn't someValue be the item at any index N instead? It’s because we know to expect something of special significance at index 2.

In that case, the only time myArray[2] will fail is when your prior knowledge breaks down. The type-system has no way to encode and check for the length of an Array, and that has allowed somebody to pass in a bad value. So what to do?

A) If you absolutely require a value for myArray[2]: Insert a precondition check.
B) If you can still continue without myArray[2]: Check the length of the Array. Your logic will be branching anyway in this case, to account for the value (and subsequent values) being/not being present.

Personally, the only valid use-case I can think of is when you want to initialise an Array’s elements out-of-order - i.e., you want to set a value for myArray[2] despite myArray[0] and [1] not being populated. In that case, it would be better to have some kind of SparseArray type, and for us to have a proper API for unsafe initialisation of stdlib types.

- Karl
_______________________________________________
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