Optional safe subscripting for arrays


(Rudolf Adamkovič) #1

Hi there!

From time to time, I find myself using safe subscripting, e.g.:

let array = [1, 2, 3]

array[safe: 0] // 1
array[safe: 1] // 2
array[safe: 2] // 3
array[safe: 3] // nil

… with the following implementation:

subscript (safe index: Int) -> Element? {
    return self.indices ~= index ? self[index] : nil
}

I was wondering … wouldn’t it be handy to have this in the standard library?

Best regards,
Rudolf Adamkovic


Add accessor with bounds check to Array
#2

I've also thought of this. But the concept of safe indexing can also be extended to CollectionType (or to the more abstract Indexable protocol) where it's index is Comparable.

For mutable Collections/MutableIndexable there could be two versions of a safe subscript set:

array[safe: 5] = 7 // does nothing if index out of bounds

array[clamp: 5] = 7
// if array is empty: does nothing
// if 5 >= array.endIndex : sets last element
// if index < 0 : sets first index

So definitely worth considering.

- Maximilian

···

Am 16.01.2016 um 21:44 schrieb Rudolf Adamkovič via swift-evolution <swift-evolution@swift.org>:

Hi there!

From time to time, I find myself using safe subscripting, e.g.:

let array = [1, 2, 3]

array[safe: 0] // 1
array[safe: 1] // 2
array[safe: 2] // 3
array[safe: 3] // nil

… with the following implementation:

subscript (safe index: Int) -> Element? {
    return self.indices ~= index ? self[index] : nil
}

I was wondering … wouldn’t it be handy to have this in the standard library?

Best regards,
Rudolf Adamkovic

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


(David Sweeris) #3

I’m definitely +1 on the getter. I have reservations on the setter. As described, this code:
subscript(safe index: Index) -> Element? {
    return self.indices ~= index ? self[index] : nil
}
subscript(clamp index: Index) -> Element? {
    get { return self.indices ~= index ? self[index] : nil } // again, because subscripts can’t be setter-only
    set {
        if self.indices ~= index {
            self[index] = newValue!
        } else {
            self.append(newValue!)
        }
    }
}

Would lead to some counter-intuitive results.
var foo = [0, 1, 2]
print(foo[safe: 2]) // Optional(2)… Yep
foo[clamp: 6] = 6
print(foo[safe: 6]) // nil… Wait, what? I just set that to 6! Why is it nil now?

Instead of just appending the new value, we could extend the array to the required capacity:
subscript(safe index: Index) -> Element? {
    get { return self.indices ~= index ? self[index] : nil }
    set {
        if self.indices ~= index {
            self[index] = newValue!
        } else {
            while !(self.indices ~= index) {
                self.append(newValue!)
            }
        }
    }
}

The results could still be unexpected:
var foo = [0, 1, 2]
print(foo) // [0, 1, 2]… Yep
foo[safe: 9] = 9
print(foo[safe: 9]) // Optional(9)… Yep
print(foo) // [0, 1, 2, 9, 9, 9, 9, 9, 9]… Wow, that’s bigger than I thought

but at least you’d be able to read from the index to which you just wrote.

- Dave Sweeris

···

On Jan 16, 2016, at 14:08, Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org> wrote:

I've also thought of this. But the concept of safe indexing can also be extended to CollectionType (or to the more abstract Indexable protocol) where it's index is Comparable.

For mutable Collections/MutableIndexable there could be two versions of a safe subscript set:

array[safe: 5] = 7 // does nothing if index out of bounds

array[clamp: 5] = 7
// if array is empty: does nothing
// if 5 >= array.endIndex : sets last element
// if index < 0 : sets first index

So definitely worth considering.

- Maximilian

Am 16.01.2016 um 21:44 schrieb Rudolf Adamkovič via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

Hi there!

From time to time, I find myself using safe subscripting, e.g.:

let array = [1, 2, 3]

array[safe: 0] // 1
array[safe: 1] // 2
array[safe: 2] // 3
array[safe: 3] // nil

… with the following implementation:

subscript (safe index: Int) -> Element? {
    return self.indices ~= index ? self[index] : nil
}

I was wondering … wouldn’t it be handy to have this in the standard library?

Best regards,
Rudolf Adamkovic

_______________________________________________
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


#4

It is true that the setter could cause some confusion in the first place but I think of these particular setters as optional chaining:

person.residence?.address = "1 Infinite Loop"

Where the `address` is not set when `residence` is nil. So it's like the array is nil/not defined if you access it out of bounds.

In addition subscripts imply O(1) access/set so the expanding array setter should be implemented as method.

- Maximilian

···

Am 17.01.2016 um 02:29 schrieb davesweeris@mac.com:

I’m definitely +1 on the getter. I have reservations on the setter. As described, this code:
subscript(safe index: Index) -> Element? {
    return self.indices ~= index ? self[index] : nil
}
subscript(clamp index: Index) -> Element? {
    get { return self.indices ~= index ? self[index] : nil } // again, because subscripts can’t be setter-only
    set {
        if self.indices ~= index {
            self[index] = newValue!
        } else {
            self.append(newValue!)
        }
    }
}

Would lead to some counter-intuitive results.
var foo = [0, 1, 2]
print(foo[safe: 2]) // Optional(2)… Yep
foo[clamp: 6] = 6
print(foo[safe: 6]) // nil… Wait, what? I just set that to 6! Why is it nil now?

Instead of just appending the new value, we could extend the array to the required capacity:
subscript(safe index: Index) -> Element? {
    get { return self.indices ~= index ? self[index] : nil }
    set {
        if self.indices ~= index {
            self[index] = newValue!
        } else {
            while !(self.indices ~= index) {
                self.append(newValue!)
            }
        }
    }
}

The results could still be unexpected:
var foo = [0, 1, 2]
print(foo) // [0, 1, 2]… Yep
foo[safe: 9] = 9
print(foo[safe: 9]) // Optional(9)… Yep
print(foo) // [0, 1, 2, 9, 9, 9, 9, 9, 9]… Wow, that’s bigger than I thought

but at least you’d be able to read from the index to which you just wrote.

- Dave Sweeris

On Jan 16, 2016, at 14:08, Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org> wrote:

I've also thought of this. But the concept of safe indexing can also be extended to CollectionType (or to the more abstract Indexable protocol) where it's index is Comparable.

For mutable Collections/MutableIndexable there could be two versions of a safe subscript set:

array[safe: 5] = 7 // does nothing if index out of bounds

array[clamp: 5] = 7
// if array is empty: does nothing
// if 5 >= array.endIndex : sets last element
// if index < 0 : sets first index

So definitely worth considering.

- Maximilian

Am 16.01.2016 um 21:44 schrieb Rudolf Adamkovič via swift-evolution <swift-evolution@swift.org>:

Hi there!

From time to time, I find myself using safe subscripting, e.g.:

let array = [1, 2, 3]

array[safe: 0] // 1
array[safe: 1] // 2
array[safe: 2] // 3
array[safe: 3] // nil

… with the following implementation:

subscript (safe index: Int) -> Element? {
    return self.indices ~= index ? self[index] : nil
}

I was wondering … wouldn’t it be handy to have this in the standard library?

Best regards,
Rudolf Adamkovic

_______________________________________________
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


(David Sweeris) #5

So, this?
subscript(safe index: Index) -> Element? {
    get { return self.indices ~= index ? self[index] : nil }
    set {
        if self.indices ~= index {
            self[index] = newValue!
        }
    }
}

The problem with that is again semantics. You’re (sorta) saying “here, let me safely store this for you”, and then it doesn’t get stored because “safe” applies to the indexing as opposed to the assignment. What about “failable”?
subscript(failable index: Index) -> Element? {
    get { return self.indices ~= index ? self[index] : nil }
    set {
        if self.indices ~= index && newValue != nil {
            self[index] = newValue!
        }
    }
}

var foo = [1] // normal
var bar = foo[failable: 2] //nil, but it’s clearly failable, so no surprise
foo[failable: 2] = 3 //nothing happens, but again it’s clearly failable, so we know the assignment might not’ve stuck

It is true that the setter could cause some confusion in the first place but I think of these particular setters as optional chaining:

person.residence?.address = "1 Infinite Loop"

Where the `address` is not set when `residence` is nil. So it’s like the array is nil/not defined if you access it out of bounds.

I really like that! I think the optional chaining syntax would make the semantics crystal clear
// note the ? after subscript
subscript?(index: Index) -> Element? {…}

var foo = [1] // normal
var bar = foo?[2] // returns an Int?, just like with [Int]?
foo?[2] = 3 // performs assignment if possible, just like with [Int]?

I don’t know what it would take to get the “?” to apply to `[]` instead of `foo`. I have no (well, little) doubt that it’s possible, but I don’t know how hard it’d be or if it’d break anything. Off the top of my head, even though the results are just like if foo were `[Int]?`, the compiled code would not be the same. If we’re only talking about “safely” (or whatever the adverb is) indexing normal arrays, it’s not an issue because there’s nothing for the “?” to unwrap, so the programmer must be referring to the “safe” variant. With [Int]? though, are we unwrapping it? I guess we’d have to since `Optional<T>` isn’t subscriptable, so… Maybe this?
var optionalfoo:[Int]? = [1]
var bar = optionalfoo??[2] // “??”, so the optional is unwrapped *and* we use the “safe" subscript. bar = nil
var crashBar = optionalfoo?[2] // “?”, so the optional is unwrapped, but we use the normal subscript and crash

I’m not at all certain what the implications of this would be for the rest of the language, though. Hmm…

- Dave Sweeris

···

On Jan 16, 2016, at 17:55, Maximilian Hünenberger <m.huenenberger@me.com> wrote:


(David Sweeris) #6

Actually, I just thought of another setter problem: everything talked about so far allows you to assign a 'Element?', which can ignored in the setter (and is in some of the code snippets I posted), but assigning 'nil' really should be a compile-time error.

Should this be split into two proposals (one for getter and one for setter), or is that premature?

- Dave Sweeris


(Zhao Xin) #7

I think this maybe a good idea for someone.

However, you add another burden by dealing with the optional. So the
choices facing the programmers are choose from bound checking to optional
dealing.

zhaoxin

···

On Sun, Jan 17, 2016 at 9:55 AM, Maximilian Hünenberger < swift-evolution@swift.org> wrote:

It is true that the setter could cause some confusion in the first place
but I think of these particular setters as optional chaining:

person.residence?.address = "1 Infinite Loop"

Where the `address` is not set when `residence` is nil. So it's like the
array is nil/not defined if you access it out of bounds.

In addition subscripts imply O(1) access/set so the expanding array setter
should be implemented as method.

- Maximilian

Am 17.01.2016 um 02:29 schrieb davesweeris@mac.com:

I’m definitely +1 on the getter. I have reservations on the setter. As
described, this code:

subscript(safe index: Index) -> Element? {
    return self.indices ~= index ? self[index] : nil
}

subscript(clamp index: Index) -> Element? {
    get { return self.indices ~= index ? self[index] : nil } // again,
because subscripts can’t be setter-only
    set {
        if self.indices ~= index {
            self[index] = newValue!
        } else {
            self.append(newValue!)
        }
    }
}

Would lead to some counter-intuitive results.

var foo = [0, 1, 2]
print(foo[safe: 2]) // Optional(2)… Yep
foo[clamp: 6] = 6
print(foo[safe: 6]) // nil… Wait, what? I just set that to 6! Why is it
nil now?

Instead of just appending the new value, we could extend the array to the
required capacity:

subscript(safe index: Index) -> Element? {
    get { return self.indices ~= index ? self[index] : nil }
    set {
        if self.indices ~= index {
            self[index] = newValue!
        } else {
            while !(self.indices ~= index) {
                self.append(newValue!)
            }
        }
    }
}

The results could still be unexpected:

var foo = [0, 1, 2]
print(foo) // [0, 1, 2]… Yep
foo[safe: 9] = 9
print(foo[safe: 9]) // Optional(9)… Yep

print(foo) // [0, 1, 2, 9, 9, 9, 9, 9, 9]… Wow, that’s bigger than I
thought

but at least you’d be able to read from the index to which you just wrote.

- Dave Sweeris

On Jan 16, 2016, at 14:08, Maximilian Hünenberger via swift-evolution < > swift-evolution@swift.org> wrote:

I've also thought of this. But the concept of safe indexing can also be
extended to CollectionType (or to the more abstract Indexable protocol)
where it's index is Comparable.

For mutable Collections/MutableIndexable there could be two versions of a
safe subscript set:

array[safe: 5] = 7 // does nothing if index out of bounds

array[clamp: 5] = 7
// if array is empty: does nothing
// if 5 >= array.endIndex : sets last element
// if index < 0 : sets first index

So definitely worth considering.

- Maximilian

Am 16.01.2016 um 21:44 schrieb Rudolf Adamkovič via swift-evolution < > swift-evolution@swift.org>:

Hi there!

From time to time, I find myself using safe subscripting, e.g.:

let array = [1, 2, 3]

array[safe: 0] // 1
array[safe: 1] // 2
array[safe: 2] // 3
array[safe: 3] // nil

… with the following implementation:

subscript (safe index: Int) -> Element? {
    return self.indices ~= index ? self[index] : nil
}

I was wondering … wouldn’t it be handy to have this in the standard
library?

Best regards,
Rudolf Adamkovic

_______________________________________________
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

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


(Rudolf Adamkovič) #8

Dave, that’s a great point. The word “safe” doesn’t really work for setters, you’re totally right.

What about this:

let array = [0, 1, 2]

array[ifExists: 0] // same as array[0]
array[ifExists: 3] // doesn’t exist, evaluates to nil

var array = [0, 1, 2]

array[ifExists: 0] = 42 // same as array[0] = 42
array[ifExists: 3] = 42 // doesn’t exist, does nothing

Reads a bit better than “failable" to me.

Ideas?

R+

···

On 17 Jan 2016, at 04:06, Dave via swift-evolution <swift-evolution@swift.org> wrote:

So, this?
subscript(safe index: Index) -> Element? {
    get { return self.indices ~= index ? self[index] : nil }
    set {
        if self.indices ~= index {
            self[index] = newValue!
        }
    }
}

The problem with that is again semantics. You’re (sorta) saying “here, let me safely store this for you”, and then it doesn’t get stored because “safe” applies to the indexing as opposed to the assignment. What about “failable”?
subscript(failable index: Index) -> Element? {
    get { return self.indices ~= index ? self[index] : nil }
    set {
        if self.indices ~= index && newValue != nil {
            self[index] = newValue!
        }
    }
}

var foo = [1] // normal
var bar = foo[failable: 2] //nil, but it’s clearly failable, so no surprise
foo[failable: 2] = 3 //nothing happens, but again it’s clearly failable, so we know the assignment might not’ve stuck

On Jan 16, 2016, at 17:55, Maximilian Hünenberger <m.huenenberger@me.com <mailto:m.huenenberger@me.com>> wrote:

It is true that the setter could cause some confusion in the first place but I think of these particular setters as optional chaining:

person.residence?.address = "1 Infinite Loop"

Where the `address` is not set when `residence` is nil. So it’s like the array is nil/not defined if you access it out of bounds.

I really like that! I think the optional chaining syntax would make the semantics crystal clear
// note the ? after subscript
subscript?(index: Index) -> Element? {…}

var foo = [1] // normal
var bar = foo?[2] // returns an Int?, just like with [Int]?
foo?[2] = 3 // performs assignment if possible, just like with [Int]?

I don’t know what it would take to get the “?” to apply to `[]` instead of `foo`. I have no (well, little) doubt that it’s possible, but I don’t know how hard it’d be or if it’d break anything. Off the top of my head, even though the results are just like if foo were `[Int]?`, the compiled code would not be the same. If we’re only talking about “safely” (or whatever the adverb is) indexing normal arrays, it’s not an issue because there’s nothing for the “?” to unwrap, so the programmer must be referring to the “safe” variant. With [Int]? though, are we unwrapping it? I guess we’d have to since `Optional<T>` isn’t subscriptable, so… Maybe this?
var optionalfoo:[Int]? = [1]
var bar = optionalfoo??[2] // “??”, so the optional is unwrapped *and* we use the “safe" subscript. bar = nil
var crashBar = optionalfoo?[2] // “?”, so the optional is unwrapped, but we use the normal subscript and crash

I’m not at all certain what the implications of this would be for the rest of the language, though. Hmm…

- Dave Sweeris

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


(Thorsten Seitz) #9

Good points!
I like ifExists: even more than failable:

-Thorsten

···

Am 17.01.2016 um 11:26 schrieb Rudolf Adamkovič via swift-evolution <swift-evolution@swift.org>:

Dave, that’s a great point. The word “safe” doesn’t really work for setters, you’re totally right.

What about this:

let array = [0, 1, 2]

array[ifExists: 0] // same as array[0]
array[ifExists: 3] // doesn’t exist, evaluates to nil

var array = [0, 1, 2]

array[ifExists: 0] = 42 // same as array[0] = 42
array[ifExists: 3] = 42 // doesn’t exist, does nothing

Reads a bit better than “failable" to me.

Ideas?

R+

On 17 Jan 2016, at 04:06, Dave via swift-evolution <swift-evolution@swift.org> wrote:

So, this?
subscript(safe index: Index) -> Element? {
    get { return self.indices ~= index ? self[index] : nil }
    set {
        if self.indices ~= index {
            self[index] = newValue!
        }
    }
}

The problem with that is again semantics. You’re (sorta) saying “here, let me safely store this for you”, and then it doesn’t get stored because “safe” applies to the indexing as opposed to the assignment. What about “failable”?
subscript(failable index: Index) -> Element? {
    get { return self.indices ~= index ? self[index] : nil }
    set {
        if self.indices ~= index && newValue != nil {
            self[index] = newValue!
        }
    }
}

var foo = [1] // normal
var bar = foo[failable: 2] //nil, but it’s clearly failable, so no surprise
foo[failable: 2] = 3 //nothing happens, but again it’s clearly failable, so we know the assignment might not’ve stuck

On Jan 16, 2016, at 17:55, Maximilian Hünenberger <m.huenenberger@me.com> wrote:

It is true that the setter could cause some confusion in the first place but I think of these particular setters as optional chaining:

person.residence?.address = "1 Infinite Loop"

Where the `address` is not set when `residence` is nil. So it’s like the array is nil/not defined if you access it out of bounds.

I really like that! I think the optional chaining syntax would make the semantics crystal clear
// note the ? after subscript
subscript?(index: Index) -> Element? {…}

var foo = [1] // normal
var bar = foo?[2] // returns an Int?, just like with [Int]?
foo?[2] = 3 // performs assignment if possible, just like with [Int]?

I don’t know what it would take to get the “?” to apply to `[]` instead of `foo`. I have no (well, little) doubt that it’s possible, but I don’t know how hard it’d be or if it’d break anything. Off the top of my head, even though the results are just like if foo were `[Int]?`, the compiled code would not be the same. If we’re only talking about “safely” (or whatever the adverb is) indexing normal arrays, it’s not an issue because there’s nothing for the “?” to unwrap, so the programmer must be referring to the “safe” variant. With [Int]? though, are we unwrapping it? I guess we’d have to since `Optional<T>` isn’t subscriptable, so… Maybe this?
var optionalfoo:[Int]? = [1]
var bar = optionalfoo??[2] // “??”, so the optional is unwrapped *and* we use the “safe" subscript. bar = nil
var crashBar = optionalfoo?[2] // “?”, so the optional is unwrapped, but we use the normal subscript and crash

I’m not at all certain what the implications of this would be for the rest of the language, though. Hmm…

- 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


(Rudolf Adamkovič) #10

All right, I put together a proposal:

https://github.com/salutis/swift-evolution/blob/master/proposals/XXXX-runtime-safe-array-subscripting.md

… and opened a PR:

https://github.com/apple/swift-evolution/pull/133

Let’s see how this goes.

R+

···

On 19 Jan 2016, at 06:53, Thorsten Seitz <tseitz42@icloud.com> wrote:

Good points!
I like ifExists: even more than failable:

-Thorsten

Am 17.01.2016 um 11:26 schrieb Rudolf Adamkovič via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

Dave, that’s a great point. The word “safe” doesn’t really work for setters, you’re totally right.

What about this:

let array = [0, 1, 2]

array[ifExists: 0] // same as array[0]
array[ifExists: 3] // doesn’t exist, evaluates to nil

var array = [0, 1, 2]

array[ifExists: 0] = 42 // same as array[0] = 42
array[ifExists: 3] = 42 // doesn’t exist, does nothing

Reads a bit better than “failable" to me.

Ideas?

R+

On 17 Jan 2016, at 04:06, Dave via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

So, this?
subscript(safe index: Index) -> Element? {
    get { return self.indices ~= index ? self[index] : nil }
    set {
        if self.indices ~= index {
            self[index] = newValue!
        }
    }
}

The problem with that is again semantics. You’re (sorta) saying “here, let me safely store this for you”, and then it doesn’t get stored because “safe” applies to the indexing as opposed to the assignment. What about “failable”?
subscript(failable index: Index) -> Element? {
    get { return self.indices ~= index ? self[index] : nil }
    set {
        if self.indices ~= index && newValue != nil {
            self[index] = newValue!
        }
    }
}

var foo = [1] // normal
var bar = foo[failable: 2] //nil, but it’s clearly failable, so no surprise
foo[failable: 2] = 3 //nothing happens, but again it’s clearly failable, so we know the assignment might not’ve stuck

On Jan 16, 2016, at 17:55, Maximilian Hünenberger <m.huenenberger@me.com <mailto:m.huenenberger@me.com>> wrote:

It is true that the setter could cause some confusion in the first place but I think of these particular setters as optional chaining:

person.residence?.address = "1 Infinite Loop"

Where the `address` is not set when `residence` is nil. So it’s like the array is nil/not defined if you access it out of bounds.

I really like that! I think the optional chaining syntax would make the semantics crystal clear
// note the ? after subscript
subscript?(index: Index) -> Element? {…}

var foo = [1] // normal
var bar = foo?[2] // returns an Int?, just like with [Int]?
foo?[2] = 3 // performs assignment if possible, just like with [Int]?

I don’t know what it would take to get the “?” to apply to `[]` instead of `foo`. I have no (well, little) doubt that it’s possible, but I don’t know how hard it’d be or if it’d break anything. Off the top of my head, even though the results are just like if foo were `[Int]?`, the compiled code would not be the same. If we’re only talking about “safely” (or whatever the adverb is) indexing normal arrays, it’s not an issue because there’s nothing for the “?” to unwrap, so the programmer must be referring to the “safe” variant. With [Int]? though, are we unwrapping it? I guess we’d have to since `Optional<T>` isn’t subscriptable, so… Maybe this?
var optionalfoo:[Int]? = [1]
var bar = optionalfoo??[2] // “??”, so the optional is unwrapped *and* we use the “safe" subscript. bar = nil
var crashBar = optionalfoo?[2] // “?”, so the optional is unwrapped, but we use the normal subscript and crash

I’m not at all certain what the implications of this would be for the rest of the language, though. Hmm…

- 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


#11

The setter of the subscript should be:

set {
    if self.indices ~= index && newValue != nil {
        self[index] = newValue!
    }
}

Since "newValue" is of type "Element?".

It seems that this subscript could also be added to "CollectionType" and "MutableCollectionType".

The setter is weird because you can use an optional element:

var arr = [1]
// is valid but doesn't set the first element
arr[ifExists: 0] = nil

var arr2: [Int?] = [1]
arr2[ifExists: 0] = nil // changes nothing
arr2[ifExists: 0] = .Some(nil) // sets first element to nil : arr2 == [nil]

I don't know whether a setter should be added at all.

- Maximilian

···

Am 31.01.2016 um 23:38 schrieb Rudolf Adamkovič via swift-evolution <swift-evolution@swift.org>:

All right, I put together a proposal:

https://github.com/salutis/swift-evolution/blob/master/proposals/XXXX-runtime-safe-array-subscripting.md

… and opened a PR:

https://github.com/apple/swift-evolution/pull/133

Let’s see how this goes.

R+


(David Sweeris) #12

My recollection from the discussion thread is that we didn't really reach a firm consensus on the setter semantics, mostly because Swift doesn't support setter-only subscripts, and we couldn't come up with one argument label that both read well as both a setter & a getter, *and* clearly meant that the assignment itself could fail.

Don't misunderstand me... I'm definitely +1 on this, I'm just not a fan of "ifExists" (no, I don't have any better ideas)

- Dave Sweeris

···

On Jan 31, 2016, at 15:07, Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org> wrote:

The setter of the subscript should be:

set {
    if self.indices ~= index && newValue != nil {
        self[index] = newValue!
    }
}

Since "newValue" is of type "Element?".

It seems that this subscript could also be added to "CollectionType" and "MutableCollectionType".

The setter is weird because you can use an optional element:

var arr = [1]
// is valid but doesn't set the first element
arr[ifExists: 0] = nil

var arr2: [Int?] = [1]
arr2[ifExists: 0] = nil // changes nothing
arr2[ifExists: 0] = .Some(nil) // sets first element to nil : arr2 == [nil]

I don't know whether a setter should be added at all.

- Maximilian

Am 31.01.2016 um 23:38 schrieb Rudolf Adamkovič via swift-evolution <swift-evolution@swift.org>:

All right, I put together a proposal:

https://github.com/salutis/swift-evolution/blob/master/proposals/XXXX-runtime-safe-array-subscripting.md

… and opened a PR:

https://github.com/apple/swift-evolution/pull/133

Let’s see how this goes.

R+

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


(David Turnbull) #13

Here's real working code you can tinker with in Swift 2.1:

extension Indexable {

    public subscript(ifExists index: Index) -> _Element? {

        get {

            if startIndex.distanceTo(index) < startIndex.distanceTo(endIndex)
{

                return self[index]

            }

            return nil

        }

    }

}

You can make the setter work and factor out some of the ugly if you wanted.
I didn't want to. I have no desire to see this in the language. I just
wanted to point out that developers who want it can have it today.

-david https://github.com/AE9RB/SwiftGL

···

On Sun, Jan 31, 2016 at 3:21 PM, David Sweeris via swift-evolution < swift-evolution@swift.org> wrote:

My recollection from the discussion thread is that we didn't really reach
a firm consensus on the setter semantics, mostly because Swift doesn't
support setter-only subscripts, and we couldn't come up with one argument
label that both read well as both a setter & a getter, *and* clearly meant
that the assignment itself could fail.

Don't misunderstand me... I'm definitely +1 on this, I'm just not a fan of
"ifExists" (no, I don't have any better ideas)

- Dave Sweeris

On Jan 31, 2016, at 15:07, Maximilian Hünenberger via swift-evolution < > swift-evolution@swift.org> wrote:

The setter of the subscript should be:

set {
    if self.indices ~= index && newValue != nil {
        self[index] = newValue!
    }
}

Since "newValue" is of type "Element?".

It seems that this subscript could also be added to "CollectionType" and
"MutableCollectionType".

The setter is weird because you can use an optional element:

var arr = [1]
// is valid but doesn't set the first element
arr[ifExists: 0] = nil

var arr2: [Int?] = [1]
arr2[ifExists: 0] = nil // changes nothing
arr2[ifExists: 0] = .Some(nil) // sets first element to nil : arr2 == [nil]

I don't know whether a setter should be added at all.

- Maximilian

Am 31.01.2016 um 23:38 schrieb Rudolf Adamkovič via swift-evolution < > swift-evolution@swift.org>:

All right, I put together a proposal:

https://github.com/salutis/swift-evolution/blob/master/proposals/XXXX-runtime-safe-array-subscripting.md

… and opened a PR:

https://github.com/apple/swift-evolution/pull/133

Let’s see how this goes.

R+

_______________________________________________
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


(Rudolf Adamkovič) #14

Hi Maximilian,

ah, I see. This is a show stopper then!

From what I imagine, this should not type-check:

var array = [1]
array[ifExists: 0] = nil

… and this should set array[0] to nil:

var array: [Int?] = [1]
array[ifExists: 0] = nil

Is it not possible to implement such setter in Swift?

R+

···

On 1 Feb 2016, at 00:07, Maximilian Hünenberger <m.huenenberger@me.com> wrote:

The setter of the subscript should be:

set {
    if self.indices ~= index && newValue != nil {
        self[index] = newValue!
    }
}

Since "newValue" is of type "Element?".

It seems that this subscript could also be added to "CollectionType" and "MutableCollectionType".

The setter is weird because you can use an optional element:

var arr = [1]
// is valid but doesn't set the first element
arr[ifExists: 0] = nil

var arr2: [Int?] = [1]
arr2[ifExists: 0] = nil // changes nothing
arr2[ifExists: 0] = .Some(nil) // sets first element to nil : arr2 == [nil]

I don't know whether a setter should be added at all.

- Maximilian

Am 31.01.2016 um 23:38 schrieb Rudolf Adamkovič via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

All right, I put together a proposal:

https://github.com/salutis/swift-evolution/blob/master/proposals/XXXX-runtime-safe-array-subscripting.md

… and opened a PR:

https://github.com/apple/swift-evolution/pull/133

Let’s see how this goes.

R+


(David Sweeris) #15

Really, it should allow the nil to go through for any type that conforms to NilLiteralConvertible, not just Optionals. This code compiles on its own, but fails when you try to use it with an optional type:
extension Array {
    subscript(failableLookup idx: Index) -> Element? {
        get { return (startIndex ..< endIndex) ~= idx ? self[idx] : nil }
        set { if (startIndex ..< endIndex) ~= idx && newValue != nil { self[idx] = newValue! } }
    }
}
extension Array where Element: NilLiteralConvertible {
    subscript(failableLookup idx: Index) -> Element? {
        get { return (startIndex ..< endIndex) ~= idx ? self[idx] : nil }
        set {
            if (startIndex ..< endIndex) ~= idx {
                switch newValue {
                case .None: self[idx] = nil
                case .Some(let nv): self[idx] = nv
                }
            }
        }
    }
}
var optarr: [Int?] = [1,2,3]
print(optarr)
optarr[failableLookup: 2] = nil // Ambiguous use of 'subscript(failableLookup:)'

I don’t understand why the compiler thinks it’s ambiguous, though. Int? is NilLiteralConvertible, so the compiler should send it to the more specialized subscript. At least I thought that was how it was supposed to work. It might be a compiler bug, though, because it’s quite confused… It claims its two choices are:
EquationTests.swift:29:9: Found this candidate
EquationTests.swift:29:9: Found this candidate

and that’s the same line that tripped the error in the first place: optarr[failableLookup: 2] = nil

Astute readers will notice two things: First, the two candidates are identical, so there’s actually only one candidate. Second, optarr[failableLookup: 2] = nil does not define a subscript function, ambiguous or otherwise.

Anyway, that’s as far as I got with it.

- Dave Sweeris

···

On Feb 1, 2016, at 00:53, Rudolf Adamkovič via swift-evolution <swift-evolution@swift.org> wrote:

Hi Maximilian,

ah, I see. This is a show stopper then!

From what I imagine, this should not type-check:

var array = [1]
array[ifExists: 0] = nil

… and this should set array[0] to nil:

var array: [Int?] = [1]
array[ifExists: 0] = nil

Is it not possible to implement such setter in Swift?

R+

On 1 Feb 2016, at 00:07, Maximilian Hünenberger <m.huenenberger@me.com <mailto:m.huenenberger@me.com>> wrote:

The setter of the subscript should be:

set {
    if self.indices ~= index && newValue != nil {
        self[index] = newValue!
    }
}

Since "newValue" is of type "Element?".

It seems that this subscript could also be added to "CollectionType" and "MutableCollectionType".

The setter is weird because you can use an optional element:

var arr = [1]
// is valid but doesn't set the first element
arr[ifExists: 0] = nil

var arr2: [Int?] = [1]
arr2[ifExists: 0] = nil // changes nothing
arr2[ifExists: 0] = .Some(nil) // sets first element to nil : arr2 == [nil]

I don't know whether a setter should be added at all.

- Maximilian

Am 31.01.2016 um 23:38 schrieb Rudolf Adamkovič via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

All right, I put together a proposal:

https://github.com/salutis/swift-evolution/blob/master/proposals/XXXX-runtime-safe-array-subscripting.md

… and opened a PR:

https://github.com/apple/swift-evolution/pull/133

Let’s see how this goes.

R+

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


(David Sweeris) #16

Ok, if the subscript label on the second one isn’t the same as the first one, it works. I’m still not sure why what I wrote earlier today was ambiguous, but this seems to work:
extension Array {
    subscript(failableLookup idx: Index) -> Element? {
        get { return (startIndex ..< endIndex) ~= idx ? self[idx] : nil }
        set { if (startIndex ..< endIndex) ~= idx && newValue != nil { self[idx] = newValue! } }
    }
}
extension Array where Element: NilLiteralConvertible {
    subscript(nilConvertible idx: Index) -> Element? {
        get { return (startIndex ..< endIndex) ~= idx ? self[idx] : nil }
        set { if (startIndex ..< endIndex) ~= idx { self[idx] = newValue ?? Element(nilLiteral: ())} }
    }
}

Seems kinda “hacky”, though, to need the 2nd set argument labels.

Anyway, I hope this helps.

- Dave Sweeris

···

On Feb 1, 2016, at 00:53, Rudolf Adamkovič via swift-evolution <swift-evolution@swift.org> wrote:

Hi Maximilian,

ah, I see. This is a show stopper then!

From what I imagine, this should not type-check:

var array = [1]
array[ifExists: 0] = nil

… and this should set array[0] to nil:

var array: [Int?] = [1]
array[ifExists: 0] = nil

Is it not possible to implement such setter in Swift?

R+

On 1 Feb 2016, at 00:07, Maximilian Hünenberger <m.huenenberger@me.com <mailto:m.huenenberger@me.com>> wrote:

The setter of the subscript should be:

set {
    if self.indices ~= index && newValue != nil {
        self[index] = newValue!
    }
}

Since "newValue" is of type "Element?".

It seems that this subscript could also be added to "CollectionType" and "MutableCollectionType".

The setter is weird because you can use an optional element:

var arr = [1]
// is valid but doesn't set the first element
arr[ifExists: 0] = nil

var arr2: [Int?] = [1]
arr2[ifExists: 0] = nil // changes nothing
arr2[ifExists: 0] = .Some(nil) // sets first element to nil : arr2 == [nil]

I don't know whether a setter should be added at all.

- Maximilian

Am 31.01.2016 um 23:38 schrieb Rudolf Adamkovič via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

All right, I put together a proposal:

https://github.com/salutis/swift-evolution/blob/master/proposals/XXXX-runtime-safe-array-subscripting.md

… and opened a PR:

https://github.com/apple/swift-evolution/pull/133

Let’s see how this goes.

R+

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


#17

I just realized that the normal setter for failable lookups is very nice in case of assigning/swapping:

extension Array {
    subscript(ifExists idx: Index) -> Element? {
        get { return (startIndex ..< endIndex) ~= idx ? self[idx] : nil }
        set { if (startIndex ..< endIndex) ~= idx && newValue != nil { self[idx] = newValue! } }
    }
}

        // array[index1] is only set if both indexes are valid
        array[ifExists: index1] = array[ifExists: index2]

if array is of type [Int?] and the special setter for optional Elements would have been added:

array[index1] would be set to "nil" if array[index2] is nil or index2 is not valid which is unfortunate.

- Maximilian

···

Am 01.02.2016 um 22:34 schrieb davesweeris@mac.com:

Really, it should allow the nil to go through for any type that conforms to NilLiteralConvertible, not just Optionals. This code compiles on its own, but fails when you try to use it with an optional type:
extension Array {
    subscript(failableLookup idx: Index) -> Element? {
        get { return (startIndex ..< endIndex) ~= idx ? self[idx] : nil }
        set { if (startIndex ..< endIndex) ~= idx && newValue != nil { self[idx] = newValue! } }
    }
}
extension Array where Element: NilLiteralConvertible {
    subscript(failableLookup idx: Index) -> Element? {
        get { return (startIndex ..< endIndex) ~= idx ? self[idx] : nil }
        set {
            if (startIndex ..< endIndex) ~= idx {
                switch newValue {
                case .None: self[idx] = nil
                case .Some(let nv): self[idx] = nv
                }
            }
        }
    }
}
var optarr: [Int?] = [1,2,3]
print(optarr)
optarr[failableLookup: 2] = nil // Ambiguous use of 'subscript(failableLookup:)'

I don’t understand why the compiler thinks it’s ambiguous, though. Int? is NilLiteralConvertible, so the compiler should send it to the more specialized subscript. At least I thought that was how it was supposed to work. It might be a compiler bug, though, because it’s quite confused… It claims its two choices are:
EquationTests.swift:29:9: Found this candidate
EquationTests.swift:29:9: Found this candidate

and that’s the same line that tripped the error in the first place: optarr[failableLookup: 2] = nil

Astute readers will notice two things: First, the two candidates are identical, so there’s actually only one candidate. Second, optarr[failableLookup: 2] = nil does not define a subscript function, ambiguous or otherwise.

Anyway, that’s as far as I got with it.

- Dave Sweeris

On Feb 1, 2016, at 00:53, Rudolf Adamkovič via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi Maximilian,

ah, I see. This is a show stopper then!

From what I imagine, this should not type-check:

var array = [1]
array[ifExists: 0] = nil

… and this should set array[0] to nil:

var array: [Int?] = [1]
array[ifExists: 0] = nil

Is it not possible to implement such setter in Swift?

R+

On 1 Feb 2016, at 00:07, Maximilian Hünenberger <m.huenenberger@me.com <mailto:m.huenenberger@me.com>> wrote:

The setter of the subscript should be:

set {
    if self.indices ~= index && newValue != nil {
        self[index] = newValue!
    }
}

Since "newValue" is of type "Element?".

It seems that this subscript could also be added to "CollectionType" and "MutableCollectionType".

The setter is weird because you can use an optional element:

var arr = [1]
// is valid but doesn't set the first element
arr[ifExists: 0] = nil

var arr2: [Int?] = [1]
arr2[ifExists: 0] = nil // changes nothing
arr2[ifExists: 0] = .Some(nil) // sets first element to nil : arr2 == [nil]

I don't know whether a setter should be added at all.

- Maximilian

Am 31.01.2016 um 23:38 schrieb Rudolf Adamkovič via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

All right, I put together a proposal:

https://github.com/salutis/swift-evolution/blob/master/proposals/XXXX-runtime-safe-array-subscripting.md

… and opened a PR:

https://github.com/apple/swift-evolution/pull/133

Let’s see how this goes.

R+

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


(David Sweeris) #18

I went back to look at this some more today, and figured it out how to do it (at least in an Xcode 7.3 beta (7D111g) playground) with only one set of argument labels:
extension MutableCollectionType {
    subscript(ifExists idx: Self.Index) -> Self.Generator.Element? {
        get {return self.indices.contains(idx) ? self[idx] : nil}
        set {
            if self.indices.contains(idx) {
                if let nv = newValue {
                    self[idx] = nv
                } else if let nilType = self[idx] as? NilLiteralConvertible {
                    self[idx] = (nilType.dynamicType.init(nilLiteral: ()) as! Self.Generator.Element)
                }
            }
        }
    }
}
extension CollectionType {
    subscript(ifExists idx: Self.Index) -> Self.Generator.Element? {
        return self.indices.contains(idx) ? self[idx] : nil
    }
}

And then to test it:
struct Foo : NilLiteralConvertible, CustomStringConvertible, IntegerLiteralConvertible {
    var value: Int
    init(nilLiteral: ()) {value = Int()}
    init(integerLiteral value: IntegerLiteralType) { self.value = value }
    var description: String { return "\(value)" }
}
var foo: [Int] = [0,1,2,3]
foo[ifExists: 3] = nil // This *should* be a compiler error, but it isn't because Swift doesn't support getters returning T? and setters takeing T (perhaps there ought to be a proposal about this). In the meantime, this doesn't do anything because you can't assign a nil to an Int.
print(foo) // prints [0, 1, 2, 3]
foo[ifExists: 3] = 4
print(foo) // prints [0, 1, 2, 4]

var bar: [Int?] = [0,1,2,3]
bar[ifExists: 3] = nil as Int?? // Again, this *should* be a compiler error… but isn’t (for the same reason as before)
print(bar) // prints [Optional(0), Optional(1), Optional(2), nil]
bar[ifExists: 2] = nil // Here, the compiler (eroneously, IMHO) treats this as Optional<Optional<Int>>.None, rather than as Optional<Optional<Int>>.Some(Optional<Int>.None), thus leading to all the trickeries needed to get the nil assignment to go through
print(bar) // prints [Optional(0), Optional(1), nil, nil]
bar[ifExists: 1] = nil as Int? // Wouldn’t ever be an error, since Element == Int?
print(bar) // prints [Optional(0), nil, nil, nil]

var bof: [Foo] = [0,1,2,3]
bof[ifExists: 3] = nil // Sets bof[3] to nil, which results in bof[3] = Foo(nilLiteral: ())
print(bof) // prints [0, 1, 2, 0]

(FWIW, I think “failableIndex” or “failableLookup” is a little clearer than “ifExists”, but it’s not keeping me up at night or anything.)

Anyway, does this address everyone’s concerns, at least within what the language allows?

- Dave Sweeris

···

On Feb 1, 2016, at 15:10, Dave via swift-evolution <swift-evolution@swift.org> wrote:

Ok, if the subscript label on the second one isn’t the same as the first one, it works. I’m still not sure why what I wrote earlier today was ambiguous, but this seems to work:
extension Array {
    subscript(failableLookup idx: Index) -> Element? {
        get { return (startIndex ..< endIndex) ~= idx ? self[idx] : nil }
        set { if (startIndex ..< endIndex) ~= idx && newValue != nil { self[idx] = newValue! } }
    }
}
extension Array where Element: NilLiteralConvertible {
    subscript(nilConvertible idx: Index) -> Element? {
        get { return (startIndex ..< endIndex) ~= idx ? self[idx] : nil }
        set { if (startIndex ..< endIndex) ~= idx { self[idx] = newValue ?? Element(nilLiteral: ())} }
    }
}

Seems kinda “hacky”, though, to need the 2nd set argument labels.

Anyway, I hope this helps.

- Dave Sweeris

On Feb 1, 2016, at 00:53, Rudolf Adamkovič via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi Maximilian,

ah, I see. This is a show stopper then!

From what I imagine, this should not type-check:

var array = [1]
array[ifExists: 0] = nil

… and this should set array[0] to nil:

var array: [Int?] = [1]
array[ifExists: 0] = nil

Is it not possible to implement such setter in Swift?

R+

On 1 Feb 2016, at 00:07, Maximilian Hünenberger <m.huenenberger@me.com <mailto:m.huenenberger@me.com>> wrote:

The setter of the subscript should be:

set {
    if self.indices ~= index && newValue != nil {
        self[index] = newValue!
    }
}

Since "newValue" is of type "Element?".

It seems that this subscript could also be added to "CollectionType" and "MutableCollectionType".

The setter is weird because you can use an optional element:

var arr = [1]
// is valid but doesn't set the first element
arr[ifExists: 0] = nil

var arr2: [Int?] = [1]
arr2[ifExists: 0] = nil // changes nothing
arr2[ifExists: 0] = .Some(nil) // sets first element to nil : arr2 == [nil]

I don't know whether a setter should be added at all.

- Maximilian

Am 31.01.2016 um 23:38 schrieb Rudolf Adamkovič via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

All right, I put together a proposal:

https://github.com/salutis/swift-evolution/blob/master/proposals/XXXX-runtime-safe-array-subscripting.md

… and opened a PR:

https://github.com/apple/swift-evolution/pull/133

Let’s see how this goes.

R+

_______________________________________________
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


(Thorsten Seitz) #19

I just realized that the normal setter for failable lookups is very nice in case of assigning/swapping:

extension Array {
    subscript(ifExists idx: Index) -> Element? {
        get { return (startIndex ..< endIndex) ~= idx ? self[idx] : nil }
        set { if (startIndex ..< endIndex) ~= idx && newValue != nil { self[idx] = newValue! } }
    }
}

        // array[index1] is only set if both indexes are valid
        array[ifExists: index1] = array[ifExists: index2]

if array is of type [Int?] and the special setter for optional Elements would have been added:

array[index1] would be set to "nil" if array[index2] is nil or index2 is not valid which is unfortunate.

Why is that unfortunate? Isn't it exactly what you specified in your assignment?

-Thorsten

···

Am 04.02.2016 um 21:24 schrieb Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org>:

- Maximilian

Am 01.02.2016 um 22:34 schrieb davesweeris@mac.com:

Really, it should allow the nil to go through for any type that conforms to NilLiteralConvertible, not just Optionals. This code compiles on its own, but fails when you try to use it with an optional type:
extension Array {
    subscript(failableLookup idx: Index) -> Element? {
        get { return (startIndex ..< endIndex) ~= idx ? self[idx] : nil }
        set { if (startIndex ..< endIndex) ~= idx && newValue != nil { self[idx] = newValue! } }
    }
}
extension Array where Element: NilLiteralConvertible {
    subscript(failableLookup idx: Index) -> Element? {
        get { return (startIndex ..< endIndex) ~= idx ? self[idx] : nil }
        set {
            if (startIndex ..< endIndex) ~= idx {
                switch newValue {
                case .None: self[idx] = nil
                case .Some(let nv): self[idx] = nv
                }
            }
        }
    }
}
var optarr: [Int?] = [1,2,3]
print(optarr)
optarr[failableLookup: 2] = nil // Ambiguous use of 'subscript(failableLookup:)'

I don’t understand why the compiler thinks it’s ambiguous, though. Int? is NilLiteralConvertible, so the compiler should send it to the more specialized subscript. At least I thought that was how it was supposed to work. It might be a compiler bug, though, because it’s quite confused… It claims its two choices are:
EquationTests.swift:29:9: Found this candidate
EquationTests.swift:29:9: Found this candidate

and that’s the same line that tripped the error in the first place: optarr[failableLookup: 2] = nil

Astute readers will notice two things: First, the two candidates are identical, so there’s actually only one candidate. Second, optarr[failableLookup: 2] = nil does not define a subscript function, ambiguous or otherwise.

Anyway, that’s as far as I got with it.

- Dave Sweeris

On Feb 1, 2016, at 00:53, Rudolf Adamkovič via swift-evolution <swift-evolution@swift.org> wrote:

Hi Maximilian,

ah, I see. This is a show stopper then!

From what I imagine, this should not type-check:

var array = [1]
array[ifExists: 0] = nil

… and this should set array[0] to nil:

var array: [Int?] = [1]
array[ifExists: 0] = nil

Is it not possible to implement such setter in Swift?

R+

On 1 Feb 2016, at 00:07, Maximilian Hünenberger <m.huenenberger@me.com> wrote:

The setter of the subscript should be:

set {
    if self.indices ~= index && newValue != nil {
        self[index] = newValue!
    }
}

Since "newValue" is of type "Element?".

It seems that this subscript could also be added to "CollectionType" and "MutableCollectionType".

The setter is weird because you can use an optional element:

var arr = [1]
// is valid but doesn't set the first element
arr[ifExists: 0] = nil

var arr2: [Int?] = [1]
arr2[ifExists: 0] = nil // changes nothing
arr2[ifExists: 0] = .Some(nil) // sets first element to nil : arr2 == [nil]

I don't know whether a setter should be added at all.

- Maximilian

Am 31.01.2016 um 23:38 schrieb Rudolf Adamkovič via swift-evolution <swift-evolution@swift.org>:

All right, I put together a proposal:

https://github.com/salutis/swift-evolution/blob/master/proposals/XXXX-runtime-safe-array-subscripting.md

… and opened a PR:

https://github.com/apple/swift-evolution/pull/133

Let’s see how this goes.

R+

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

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


(Haravikk) #20

Wouldn’t the return type be Int?? in this case? It’s not as pretty to test for as a plain Int? but iirc you can still distinguish a return type of nil from an optional that happens to contain nil, which should allow you to tell the difference between a nil value and an invalid index, I just can’t recall how at the moment (as I design around cases like these like my life depends on it :wink:

···

On 4 Feb 2016, at 20:24, Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org> wrote:

I just realized that the normal setter for failable lookups is very nice in case of assigning/swapping:

extension Array {
    subscript(ifExists idx: Index) -> Element? {
        get { return (startIndex ..< endIndex) ~= idx ? self[idx] : nil }
        set { if (startIndex ..< endIndex) ~= idx && newValue != nil { self[idx] = newValue! } }
    }
}

        // array[index1] is only set if both indexes are valid
        array[ifExists: index1] = array[ifExists: index2]

if array is of type [Int?] and the special setter for optional Elements would have been added:

array[index1] would be set to "nil" if array[index2] is nil or index2 is not valid which is unfortunate.