Add accessor with bounds check to Array


(Jon Hull) #122

What about "tolerant:"?

let x:Int? = myInts[tolerant: 0]

Also "lax:" is short :slight_smile:

let x:Int? = myInts[lax: 0]

(Andrew Trick) #123

This please:
let x:Int? = myInts[probe: 0]

What fun it is to bikeshed.

(Erik Little) #124

Would it be possible to just move forward a proposal without a defined name, and let the core team decide from the suggested options (or come up with a new one)?

This feels like the kind of thing where we mostly agree on the functionality, but really struggle on the naming.

(Erica Sadun) #125

I've come to the unexpected conclusion over time that I do not want to add a range-checked index. I could not find any use-cases outside of sample code that I need this feature for.

If you can provide some real world, real code use-cases, I would probably swing around back to being "for" instead of "against".

(Xiaodi Wu) #126

I would disagree pretty strongly with this approach. It's the role of the community to grapple with the naming, which is a key part of designing the language.

(Erik Little) #127

I merely say this because of the recent Result proposal. That was one of those cases where the functionality initially presented was revised substantially and immediately put forth for re-review.

I think what we need at this point is a champion to put forward a written proposal and an accompanying implementation.

(Xiaodi Wu) #128

The recent Result proposal went through at least three rounds of design before review. It was put forward for review without a consensus design in no small part because the core team felt it was important to have the process completed for Swift 5, for reasons they outlined. Therefore, it is an aberration strenously to be avoided without good reason, not a precedent.

There are at least two versions of this proposal already written; the implementation isn't going to be a barrier either.

(Daryle Walker) #129

Maybe because if you have some random Index value that you're going to splat on some Collection's subscript without any clue (in advance) if said index value is valid, then your algorithm's design is already fundamentally broken?! (Maybe something like this could be added to the commonly-rejected rationale.)

There is the earlier case by @jawbroken where s/he had to check a cell's neighbors in a multi-dimensional grid, and nil-returns help avoid special-casing the (literal) edge and corner cases, but most uses are probably not like this. Just run a loop from startIndex to endIndex, or vice-versa for bi-directional collections, and sprinkle in break and/or continue when you don't need to visit every element. Use zip or Sequence.enumerated() when you need to track counts.

Due to array slices, an integer index is not necessarily an offset. Maybe we need an alternate subscript to track offsets. I think that has been proposed before too.


Though, thinking more about this now, maybe a subscript(_:default:) on Array, roughly matching the one on Dictionary but being get-only, would cover most use cases and also rescue us all from naming concerns.

(Rudolf Adamkovič) #131

I need this all the time when parsing files, e.g. migrating legacy state for puzzle games, reading column-based dictionary files, etc. I want my parsers to throw when things go wrong instead of crashing. Checking indices works but is not elegant, and one needs to be very careful to keep everything in sync. Note that in order for this to be useful for me, it would need to work not only for Array but also for Data and include not only at-index but also in-range variant.

(Daryle Walker) #132

What do you expect to happen if only some of the input range represents valid indexes?

(Rudolf Adamkovič) #133

Speaking of the three puzzle apps I mentioned upthread, I expect the following:

  1. The accessor returns nil
  2. The parser throws an Error

Say this happens while the app is launching. When it does, instead of crashing the entire app, the user can play all the unaffected puzzles. Currently, the app shows an alert with a "Contact Support" button, with remote logging (where no user interaction needed) as a possible future improvement. This approach allows for better diagnostics compared to (ofter terrible) crash reports, and the user can always play their puzzles. Win-win!

(Tino) #134

For me, the expected result is rather obvious when requesting a range:
Return all elements that are in the input range - so if there is no overlap with the valid indexes, just return an empty slice.

(Frank Swarbrick) #135

The following isn't technically "real world", but I felt a need for it with this test I was trying:

import Foundation

precedencegroup HighPrecedence {
    higherThan: BitwiseShiftPrecedence

infix operator .? : HighPrecedence 

extension Array {
    static func .? (s: Array, i: Int) -> Element? { return s.indices.contains(i) ? s[i] : nil }

extension FixedWidthInteger {
    init?(optional s: String?) {
        guard let str = s, let int = Self(str) else { return nil }
        self = int

if let args = CommandLine.arguments .? 1 {
    switch args {
    case "e":
        let rc = Int32(optional: CommandLine.arguments .? 2) ?? 0
        print("Exit with exit code \(rc)")
    case "a": 
        print("Abort exit")
    case "f":
    	fatalError("Fatal Error exit")

print("Normal exit")

Using the .? operator is just an example. I think [?: index] might make sense if the compiler could support it.

(Daryle Walker) #136

So, would it be a [Index: Element], mapping the actually valid indexes to their respective value? (I first thought of a (indices: Range<Index>, elements: Subsequence) tuple, but the valid indexes may not be contiguous.) This would require Index to be Hashable.

(Tino) #137

Afaics the discussion has a focus on types whose Index is Int, so Hashable can be assumed anyways, and I'd rather describe it as the requested range being clamped with the range of valid indexes.