Optional safe subscripting for arrays

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

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

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

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

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

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.

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.

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:

You are right, of course!

Actually the code as written above already works exactly like that (I just tried on IBM's Swift sandbox): if the second index is out of bounds, the value won't get changed, otherwise it will be changed and the new value will be nil if the array did contain nil at the position of the second index.

-Thorsten

···

Am 05.02.2016 um 10:20 schrieb Haravikk via swift-evolution <swift-evolution@swift.org>:

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

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

You are totally right. The return type is "Int??".

My point was that if we allowed something like this (as suggested by Dave Sweeris I think):

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

To set the element at index 0 to nil instead of doing nothing.
The next example would also set index 0 to nil even though the getter failed:

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

- Maximilian

···

Am 05.02.2016 um 10:20 schrieb Haravikk <swift-evolution@haravikk.me>:

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.

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:

There's a pretty big difference between "nil" and "Some(nil)" (and "Some(Some(1))"). This was covered pretty early on in the Apple Swift blog <https://developer.apple.com/swift/blog/?id=12> While double optionals can be confusing for humans, the compiler ought to pretty much always do something sensible.

…though I did say "ought to"; the compiler has done some less-than-sensible things in overload resolution before…

Jordan

···

On Feb 5, 2016, at 15:58, Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org> wrote:

You are totally right. The return type is "Int??".

My point was that if we allowed something like this (as suggested by Dave Sweeris I think):

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

To set the element at index 0 to nil instead of doing nothing.
The next example would also set index 0 to nil even though the getter failed:

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

- Maximilian

Am 05.02.2016 um 10:20 schrieb Haravikk <swift-evolution@haravikk.me <mailto:swift-evolution@haravikk.me>>:

On 4 Feb 2016, at 20:24, Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org <mailto: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.

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:

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

Has it been considered to just do this:

extension CollectionType {
    func at(index: Index) -> Generator.Element? {
        return self.indices ~= index ? self[index] : nil
    }
    func update(value: Generator.Element, atIndex: Index) -> Generator.Element?
{
        guard self.indices ~= index else { return nil }
        let oldValue = self[index]
        self[index] = value
        return oldValue
    }
}

Compare:
     let x = array[safe: index]
     let y = array.at(index)

It's more concise (for the getter), doesn't have to introduce new syntax,
works in current swift, and it doesn't have ambiguity about nil in a
subscript setter.

There's precedent for the update function in Dictionary:
    public mutating func updateValue(value: Value, forKey key: Key) ->
Value?

It would be a shame (and surprising/unsafe) to have to do this:

    array[safe: index] = .Some(nil) // stores nil
    array[safe: index] = nil // deletes a value

···

On Sat, Feb 6, 2016 at 10:58 AM, Maximilian Hünenberger < swift-evolution@swift.org> wrote:

You are totally right. The return type is "Int??".

My point was that if we allowed something like this (as suggested by Dave
Sweeris I think):

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

To set the element at index 0 to nil instead of doing nothing.
The next example would also set index 0 to nil even though the getter
failed:

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

- Maximilian

Am 05.02.2016 um 10:20 schrieb Haravikk <swift-evolution@haravikk.me>:

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.

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:

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

You are totally right. The return type is "Int??".

My point was that if we allowed something like this (as suggested by Dave Sweeris I think):

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

To set the element at index 0 to nil instead of doing nothing.
The next example would also set index 0 to nil even though the getter failed:

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

No, it doesn't. Just try it out.

-Thorsten

···

Am 06.02.2016 um 00:58 schrieb Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org>:

- Maximilian

Am 05.02.2016 um 10:20 schrieb Haravikk <swift-evolution@haravikk.me>:

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.

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:

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

Inline:

Has it been considered to just do this:

extension CollectionType {
    func at(index: Index) -> Generator.Element? {
        return self.indices ~= index ? self[index] : nil
    }
    func update(value: Generator.Element, atIndex: Index) -> Generator.Element? {
        guard self.indices ~= index else { return nil }
        let oldValue = self[index]
        self[index] = value
        return oldValue
    }
}

Compare:
     let x = array[safe: index]
     let y = array.at(index)

It's more concise (for the getter), doesn't have to introduce new syntax, works in current swift, and it doesn't have ambiguity about nil in a subscript setter.

Although it is shorter I think an additional safe index access is a small tweak of the normal index access. Therefore it should be a subscript.
Furthermore both method names don't indicate that they could fail.
Also consider this example:

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

        // vs

        array.at(1).map{ array.update($0, atIndex: 0) }

        if let newElement = array.at(1) {
            array.update(newValue, atIndex: 0)
        }

There's precedent for the update function in Dictionary:
    public mutating func updateValue(value: Value, forKey key: Key) -> Value?

It would be a shame (and surprising/unsafe) to have to do this:

    array[safe: index] = .Some(nil) // stores nil

You only have to use this if "array" is of type "[Int?]" but how often do you use such a type?

    array[safe: index] = nil // deletes a value

This doesn't delete a value. It does nothing.

- Maximilian

···

Am 06.02.2016 um 01:20 schrieb Andrew Bennett <cacoyi@gmail.com>:

On Sat, Feb 6, 2016 at 10:58 AM, Maximilian Hünenberger <swift-evolution@swift.org> wrote:
You are totally right. The return type is "Int??".

My point was that if we allowed something like this (as suggested by Dave Sweeris I think):

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

To set the element at index 0 to nil instead of doing nothing.
The next example would also set index 0 to nil even though the getter failed:

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

- Maximilian

Am 05.02.2016 um 10:20 schrieb Haravikk <swift-evolution@haravikk.me>:

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.

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:

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

Probably I wasn't clear about that. Sorry.

My concern was about adding (code from Dave Sweeris):

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

Since it would allow this:

var array: [Int?] = [1]
array[ifExists: 0] = nil // sets array[0] to nil if index is valid
print(array) // "[nil]"
array = [1]
array[ifExists: 0] = array[ifExists: 1]
print(array) // "[nil]"

Whereas the normal behavior:

var array: [Int?] = [1]
array[ifExists: 0] = nil // does nothing
print(array) // "[1]"
array[ifExists: 0] = array[ifExists: 1] // does nothing
print(array) // "[1]"

Hope this clarifies my point
- Maximilian

···

Am 06.02.2016 um 21:31 schrieb Thorsten Seitz <tseitz42@icloud.com>:

Am 06.02.2016 um 00:58 schrieb Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org>:

You are totally right. The return type is "Int??".

My point was that if we allowed something like this (as suggested by Dave Sweeris I think):

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

To set the element at index 0 to nil instead of doing nothing.
The next example would also set index 0 to nil even though the getter failed:

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

No, it doesn't. Just try it out.

-Thorsten

- Maximilian

Am 05.02.2016 um 10:20 schrieb Haravikk <swift-evolution@haravikk.me>:

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.

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:

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

Ah, ok, I think I understand your objection now. Let me try to summarize…

If we extend on MutableCollectionType like this:
subscript(ifExists idx: Self.Index) -> Self.Generator.Element? {
    get { return self.indices.contains(idx) ? self[idx] : nil as Self.Generator.Element? }
    set { if let nv = newValue where self.indices.contains(idx) { self[idx] = nv } }
}
then, when CollectionType.Generator.Element is NilLiteralConvertible, we have the following problem:
var array: [Int?] = [1]
array[ifExists: 0] = nil // *PROBLEM*: The most straight-forward way of safely setting something to nil silently fails because the compiler treats this as an Int?? instead of an Int?
print(array) // "[Optional(1)]"
array[ifExists: 0] = array[ifExists: 1] // *No problem*
print(array) // "[Optional(1)]"
array[ifExists: 0] = nil as Int? // *No problem*
print(array) // "[nil]"

But if we fix it to allow an unannotated nil to go through:
subscript(ifExists idx: Self.Index) -> Self.Generator.Element? {
    get { return self.indices.contains(idx) ? self[idx] : nil as Self.Generator.Element? }
    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)
                }
            }
        }
    }
}
then we have a different problem:
var array: [Int?] = [1, 2, 3]
array[ifExists: 0] = nil // *No problem*: The code no longer cares that this is an Int?? instead of an Int?
print(array) // "[nil, Optional(2), Optional(3)]"
array[ifExists: 1] = array[ifExists: 3] // *PROBLEM*: This shouldn’t do anything, but it succeeds because the code no longer cares that it’s an Int?? instead of an Int?
print(array) // "[nil, nil, Optional(3)]"
array[ifExists: 2] = nil as Int? // *No problem*
print(array) // "[nil, nil, nil]"

Assuming we’re all on the same page now… Yeah, as much as I’d love to keep the array[ifExists: 0] = nil behavior from the “fixed” version, I agree that the 2nd problem is clearly worse than the 1st, especially since being explicit about your nils (array[ifExists: 0] = nil as Int?) allows the assignment to go through.

So I’m in favor of the 1st one, which doesn’t allow unannotated nil assignments to succeed.

The only alternative I can think of is changing the language to allow subscripts to throw errors:
enum IndexError : ErrorType { case outOfRange }
subscript(throwing idx: Self.Index) throws -> Self.Generator.Element {
    get {
        guard self.indices.contains(idx) else { throw IndexError.outOfRange }
        return self[idx]
    }
    set {
        guard self.indices.contains(idx) else { throw IndexError.outOfRange }
        self[idx] = newValue
    }
}

Although… They aren’t mutually exclusive… It’s obviously not hard to imagine scenarios in which you don’t really care that you're out of bounds and just want to avoid crashing (otherwise we wouldn’t have been having this conversation in the first place) but nor is it hard to imagine scenarios in which you might want explicit confirmation that there wasn’t an error, rather than having to check for nil or compare before & after versions of the array. In those cases the throwing version would be appropriate. Plus, if your subscript function doesn’t only pass-through to an underlying collection, it might be handy to be able to throw, say, an OutOfMemory error if your subscript function loads or generates some large data structure. Or perhaps you’re writing a file manager, and you’re trying to get files by “subscripting” directories… The file may exist, but you might not have read permission.

Should we propose both?

- Dave Sweeris

···

On Feb 6, 2016, at 12:52, Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org> wrote:

Probably I wasn't clear about that. Sorry.

My concern was about adding (code from Dave Sweeris):

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

Since it would allow this:

var array: [Int?] = [1]
array[ifExists: 0] = nil // sets array[0] to nil if index is valid
print(array) // "[nil]"
array = [1]
array[ifExists: 0] = array[ifExists: 1]
print(array) // "[nil]"

Whereas the normal behavior:

var array: [Int?] = [1]
array[ifExists: 0] = nil // does nothing
print(array) // "[1]"
array[ifExists: 0] = array[ifExists: 1] // does nothing
print(array) // "[1]"

Hope this clarifies my point
- Maximilian

Am 06.02.2016 um 21:31 schrieb Thorsten Seitz <tseitz42@icloud.com <mailto:tseitz42@icloud.com>>:

Am 06.02.2016 um 00:58 schrieb Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

You are totally right. The return type is "Int??".

My point was that if we allowed something like this (as suggested by Dave Sweeris I think):

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

To set the element at index 0 to nil instead of doing nothing.
The next example would also set index 0 to nil even though the getter failed:

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

No, it doesn't. Just try it out.

-Thorsten

- Maximilian

Am 05.02.2016 um 10:20 schrieb Haravikk <swift-evolution@haravikk.me <mailto:swift-evolution@haravikk.me>>:

On 4 Feb 2016, at 20:24, Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org <mailto: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.

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:

_______________________________________________
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

If implemented via extension, you could special case Self.Generator.Element:NilLiteralConvertable. Would that help? You could say in that case that an Optional.None newValue is actually Optional<Element>.Some(Element(nilLiteral:())).

-DW

···

On Feb 6, 2016, at 4:19 PM, Dave via swift-evolution <swift-evolution@swift.org> wrote:

Ah, ok, I think I understand your objection now. Let me try to summarize…

If we extend on MutableCollectionType like this:
subscript(ifExists idx: Self.Index) -> Self.Generator.Element? {
    get { return self.indices.contains(idx) ? self[idx] : nil as Self.Generator.Element? }
    set { if let nv = newValue where self.indices.contains(idx) { self[idx] = nv } }
}
then, when CollectionType.Generator.Element is NilLiteralConvertible, we have the following problem:
var array: [Int?] = [1]
array[ifExists: 0] = nil // *PROBLEM*: The most straight-forward way of safely setting something to nil silently fails because the compiler treats this as an Int?? instead of an Int?
print(array) // "[Optional(1)]"
array[ifExists: 0] = array[ifExists: 1] // *No problem*
print(array) // "[Optional(1)]"
array[ifExists: 0] = nil as Int? // *No problem*
print(array) // "[nil]"

But if we fix it to allow an unannotated nil to go through:
subscript(ifExists idx: Self.Index) -> Self.Generator.Element? {
    get { return self.indices.contains(idx) ? self[idx] : nil as Self.Generator.Element? }
    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)
                }
            }
        }
    }
}
then we have a different problem:
var array: [Int?] = [1, 2, 3]
array[ifExists: 0] = nil // *No problem*: The code no longer cares that this is an Int?? instead of an Int?
print(array) // "[nil, Optional(2), Optional(3)]"
array[ifExists: 1] = array[ifExists: 3] // *PROBLEM*: This shouldn’t do anything, but it succeeds because the code no longer cares that it’s an Int?? instead of an Int?
print(array) // "[nil, nil, Optional(3)]"
array[ifExists: 2] = nil as Int? // *No problem*
print(array) // "[nil, nil, nil]"

Assuming we’re all on the same page now… Yeah, as much as I’d love to keep the array[ifExists: 0] = nil behavior from the “fixed” version, I agree that the 2nd problem is clearly worse than the 1st, especially since being explicit about your nils (array[ifExists: 0] = nil as Int?) allows the assignment to go through.

So I’m in favor of the 1st one, which doesn’t allow unannotated nil assignments to succeed.

The only alternative I can think of is changing the language to allow subscripts to throw errors:
enum IndexError : ErrorType { case outOfRange }
subscript(throwing idx: Self.Index) throws -> Self.Generator.Element {
    get {
        guard self.indices.contains(idx) else { throw IndexError.outOfRange }
        return self[idx]
    }
    set {
        guard self.indices.contains(idx) else { throw IndexError.outOfRange }
        self[idx] = newValue
    }
}

Although… They aren’t mutually exclusive… It’s obviously not hard to imagine scenarios in which you don’t really care that you're out of bounds and just want to avoid crashing (otherwise we wouldn’t have been having this conversation in the first place) but nor is it hard to imagine scenarios in which you might want explicit confirmation that there wasn’t an error, rather than having to check for nil or compare before & after versions of the array. In those cases the throwing version would be appropriate. Plus, if your subscript function doesn’t only pass-through to an underlying collection, it might be handy to be able to throw, say, an OutOfMemory error if your subscript function loads or generates some large data structure. Or perhaps you’re writing a file manager, and you’re trying to get files by “subscripting” directories… The file may exist, but you might not have read permission.

Should we propose both?

- Dave Sweeris

On Feb 6, 2016, at 12:52, Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Probably I wasn't clear about that. Sorry.

My concern was about adding (code from Dave Sweeris):

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

Since it would allow this:

var array: [Int?] = [1]
array[ifExists: 0] = nil // sets array[0] to nil if index is valid
print(array) // "[nil]"
array = [1]
array[ifExists: 0] = array[ifExists: 1]
print(array) // "[nil]"

Whereas the normal behavior:

var array: [Int?] = [1]
array[ifExists: 0] = nil // does nothing
print(array) // "[1]"
array[ifExists: 0] = array[ifExists: 1] // does nothing
print(array) // "[1]"

Hope this clarifies my point
- Maximilian

Am 06.02.2016 um 21:31 schrieb Thorsten Seitz <tseitz42@icloud.com <mailto:tseitz42@icloud.com>>:

Am 06.02.2016 um 00:58 schrieb Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

You are totally right. The return type is "Int??".

My point was that if we allowed something like this (as suggested by Dave Sweeris I think):

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

To set the element at index 0 to nil instead of doing nothing.
The next example would also set index 0 to nil even though the getter failed:

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

No, it doesn't. Just try it out.

-Thorsten

- Maximilian

Am 05.02.2016 um 10:20 schrieb Haravikk <swift-evolution@haravikk.me <mailto:swift-evolution@haravikk.me>>:

On 4 Feb 2016, at 20:24, Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org <mailto: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.

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:

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

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

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

I tried that already, and the complier complained that something was ambiguous. But when I did it again just now to get the actual error message, it worked! I tried switching back to extending Array instead of CollectionType, and the ambiguities reappeared:
bar[ifExists: 3] = nil // Ambiguous use of ’subscript(ifExists:)’

Now, I would like to know why it’s ambiguous in one case and not the other, but it’s kind of a moot point since even when it’s working, it still allows this counter-intuitive assignment to go through:
var bar: [Int?] = [1]
bar[ifExists: 0] = bar[ifExists: 6]
print(bar) // prints [nil], should print [Optional(1)]

The problem is that an unannotated nil: bar[ifExists: 3] = nil always resolves to Optional<Element>.None, but in the case where Element is itself NilLiteralConvertible, say when Element == Int?, we need the nil to resolve to Optional<Optional<Int>>.Some(Optional<Int>.None). It’s not hard to explicitly tell the compiler to make it the “correct” kind of nil: bar[ifExists: 3] = nil as Int?, but I was hoping to avoid the extra syntax, since nobody writes foo = nil unless they’re actually trying to set foo to be nil. Unfortunately, that means the setter has to accept Optional<T:NilLiteralConvertible>.None as a T, which makes it impossible, in the setter anyway, to tell the difference between:
var bar: [Int?] = [1, 2]
bar[ifExists: 0] = nil //the bar[ifExists: 0] setter promotes the Optional<Optional<Int>>.None to an Optional<Int>.None, and then assigns it to bar[0]
and
bar[ifExists: 1] = bar[ifExists: 6] // the bar[ifExists: 6] getter returns a Optional<Optional<Int>>.None, which the bar[ifExists: 1] setter then “promotes” to an Optional<Int>.None, and then assigns it to bar[1]

And this leads to the results of a lookup silently failing and then propagating through the assignment.

I think stuff like this is at least part of why the optional system is getting reworked.

- Dave Sweeris

···

On Feb 6, 2016, at 16:02, David Waite <david@alkaline-solutions.com> wrote:

If implemented via extension, you could special case Self.Generator.Element:NilLiteralConvertable. Would that help? You could say in that case that an Optional.None newValue is actually Optional<Element>.Some(Element(nilLiteral:())).

-DW

On Feb 6, 2016, at 4:19 PM, Dave via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Ah, ok, I think I understand your objection now. Let me try to summarize…

If we extend on MutableCollectionType like this:
subscript(ifExists idx: Self.Index) -> Self.Generator.Element? {
    get { return self.indices.contains(idx) ? self[idx] : nil as Self.Generator.Element? }
    set { if let nv = newValue where self.indices.contains(idx) { self[idx] = nv } }
}
then, when CollectionType.Generator.Element is NilLiteralConvertible, we have the following problem:
var array: [Int?] = [1]
array[ifExists: 0] = nil // *PROBLEM*: The most straight-forward way of safely setting something to nil silently fails because the compiler treats this as an Int?? instead of an Int?
print(array) // "[Optional(1)]"
array[ifExists: 0] = array[ifExists: 1] // *No problem*
print(array) // "[Optional(1)]"
array[ifExists: 0] = nil as Int? // *No problem*
print(array) // "[nil]"

But if we fix it to allow an unannotated nil to go through:
subscript(ifExists idx: Self.Index) -> Self.Generator.Element? {
    get { return self.indices.contains(idx) ? self[idx] : nil as Self.Generator.Element? }
    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)
                }
            }
        }
    }
}
then we have a different problem:
var array: [Int?] = [1, 2, 3]
array[ifExists: 0] = nil // *No problem*: The code no longer cares that this is an Int?? instead of an Int?
print(array) // "[nil, Optional(2), Optional(3)]"
array[ifExists: 1] = array[ifExists: 3] // *PROBLEM*: This shouldn’t do anything, but it succeeds because the code no longer cares that it’s an Int?? instead of an Int?
print(array) // "[nil, nil, Optional(3)]"
array[ifExists: 2] = nil as Int? // *No problem*
print(array) // "[nil, nil, nil]"

Assuming we’re all on the same page now… Yeah, as much as I’d love to keep the array[ifExists: 0] = nil behavior from the “fixed” version, I agree that the 2nd problem is clearly worse than the 1st, especially since being explicit about your nils (array[ifExists: 0] = nil as Int?) allows the assignment to go through.

So I’m in favor of the 1st one, which doesn’t allow unannotated nil assignments to succeed.

The only alternative I can think of is changing the language to allow subscripts to throw errors:
enum IndexError : ErrorType { case outOfRange }
subscript(throwing idx: Self.Index) throws -> Self.Generator.Element {
    get {
        guard self.indices.contains(idx) else { throw IndexError.outOfRange }
        return self[idx]
    }
    set {
        guard self.indices.contains(idx) else { throw IndexError.outOfRange }
        self[idx] = newValue
    }
}

Although… They aren’t mutually exclusive… It’s obviously not hard to imagine scenarios in which you don’t really care that you're out of bounds and just want to avoid crashing (otherwise we wouldn’t have been having this conversation in the first place) but nor is it hard to imagine scenarios in which you might want explicit confirmation that there wasn’t an error, rather than having to check for nil or compare before & after versions of the array. In those cases the throwing version would be appropriate. Plus, if your subscript function doesn’t only pass-through to an underlying collection, it might be handy to be able to throw, say, an OutOfMemory error if your subscript function loads or generates some large data structure. Or perhaps you’re writing a file manager, and you’re trying to get files by “subscripting” directories… The file may exist, but you might not have read permission.

Should we propose both?

- Dave Sweeris

On Feb 6, 2016, at 12:52, Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Probably I wasn't clear about that. Sorry.

My concern was about adding (code from Dave Sweeris):

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

Since it would allow this:

var array: [Int?] = [1]
array[ifExists: 0] = nil // sets array[0] to nil if index is valid
print(array) // "[nil]"
array = [1]
array[ifExists: 0] = array[ifExists: 1]
print(array) // "[nil]"

Whereas the normal behavior:

var array: [Int?] = [1]
array[ifExists: 0] = nil // does nothing
print(array) // "[1]"
array[ifExists: 0] = array[ifExists: 1] // does nothing
print(array) // "[1]"

Hope this clarifies my point
- Maximilian

Am 06.02.2016 um 21:31 schrieb Thorsten Seitz <tseitz42@icloud.com <mailto:tseitz42@icloud.com>>:

Am 06.02.2016 um 00:58 schrieb Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

You are totally right. The return type is "Int??".

My point was that if we allowed something like this (as suggested by Dave Sweeris I think):

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

To set the element at index 0 to nil instead of doing nothing.
The next example would also set index 0 to nil even though the getter failed:

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

No, it doesn't. Just try it out.

-Thorsten

- Maximilian

Am 05.02.2016 um 10:20 schrieb Haravikk <swift-evolution@haravikk.me <mailto:swift-evolution@haravikk.me>>:

On 4 Feb 2016, at 20:24, Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org <mailto: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.

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:

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

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

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

Ah ok, I was using the other version which distinguishes between nil and Optional(nil):

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! } }
    }
}

Where you can assign Optional(nil) to set a value to nil which is admittedly not very intuitive...

var array: [Int?] = [1]
array[ifExists: 0] = Optional(nil)
print(array) // "[nil]"
array = [1]
array[ifExists: 0] = array[ifExists: 1]
print(array) // "[Optional(1)]"

A solution would probably be to introduce another enum mimicking Optional but built for the array subscript which allows to distinguish both cases.

-Thorsten

···

Am 06.02.2016 um 21:52 schrieb Maximilian Hünenberger <m.huenenberger@me.com>:

var array: [Int?] = [1]
array[ifExists: 0] = nil // sets array[0] to nil if index is valid
print(array) // "[nil]"
array = [1]
array[ifExists: 0] = array[ifExists: 1]
print(array) // "[nil]"

Trying to think outside the box here.

From limited testing I have done, the compiler does force both setter and getter to use the exact same optional wrapping, which may have driven part of the design so far. But with subscript which can fail, it could make sense to have the getter wrap Element in an optional, and have the setter use Element directly. Seems to make sense when comparing to pop()/push() operations; the first return wrapped Element, the second assigned no-wrapped Element.

This would of course prevent doing

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

but this could solved by adding a new assignment operator

array[ifExists: 0] ?= array[ifExists: 1]

Which (try to) assign unwrapped rhs to lhs iff rhs is not nil. This could be useful in other scenario and has been thankfully not stolen by SE024 Optional Value Setter. Unfortunately, this could bring a request for the triple question-mark assignment: assign unwrapped rhs to lhs iff both rhs and lhs are not nil, but I’m diverting here.

A quick test with:
infix operator ?= { associativity left precedence 140 assignment}
func ?=<T>(left: T, right: T?) -> T {
    return right ?? left
}

And a [ifExistSet:] and [ifExistGet:] variant look promising; but I let others dig deeper, as I do not have a use case for the setter. On the other hand, I might have some interest in the getter, which bring me to should we investigate the syntax:

array[?1]

(Recap: both array?[1] and array[1]? with any number of ? already have a meaning)
I’m suggesting a brief syntax due to a possible use case I stumble upon.

var str:String = "some user provided data with possibly some trailing garbage"
let tokens = str.componentsSeparatedByString(" ")
switch (tokens[?0], tokens[?1], tokens[?2], tokens[?3]) // Expect 0 to 3 components
{
    case (let a?, nil, nil, nil): /* Some stuff when only one token */
}

Dany

···

Le 6 févr. 2016 à 22:48, Dave via swift-evolution <swift-evolution@swift.org> a écrit :

I tried that already, and the complier complained that something was ambiguous. But when I did it again just now to get the actual error message, it worked! I tried switching back to extending Array instead of CollectionType, and the ambiguities reappeared:
bar[ifExists: 3] = nil // Ambiguous use of ’subscript(ifExists:)’

Now, I would like to know why it’s ambiguous in one case and not the other, but it’s kind of a moot point since even when it’s working, it still allows this counter-intuitive assignment to go through:
var bar: [Int?] = [1]
bar[ifExists: 0] = bar[ifExists: 6]
print(bar) // prints [nil], should print [Optional(1)]

The problem is that an unannotated nil: bar[ifExists: 3] = nil always resolves to Optional<Element>.None, but in the case where Element is itself NilLiteralConvertible, say when Element == Int?, we need the nil to resolve to Optional<Optional<Int>>.Some(Optional<Int>.None). It’s not hard to explicitly tell the compiler to make it the “correct” kind of nil: bar[ifExists: 3] = nil as Int?, but I was hoping to avoid the extra syntax, since nobody writes foo = nil unless they’re actually trying to set foo to be nil. Unfortunately, that means the setter has to accept Optional<T:NilLiteralConvertible>.None as a T, which makes it impossible, in the setter anyway, to tell the difference between:
var bar: [Int?] = [1, 2]
bar[ifExists: 0] = nil //the bar[ifExists: 0] setter promotes the Optional<Optional<Int>>.None to an Optional<Int>.None, and then assigns it to bar[0]
and
bar[ifExists: 1] = bar[ifExists: 6] // the bar[ifExists: 6] getter returns a Optional<Optional<Int>>.None, which the bar[ifExists: 1] setter then “promotes” to an Optional<Int>.None, and then assigns it to bar[1]

And this leads to the results of a lookup silently failing and then propagating through the assignment.

I think stuff like this is at least part of why the optional system is getting reworked.

- Dave Sweeris

On Feb 6, 2016, at 16:02, David Waite <david@alkaline-solutions.com <mailto:david@alkaline-solutions.com>> wrote:

If implemented via extension, you could special case Self.Generator.Element:NilLiteralConvertable. Would that help? You could say in that case that an Optional.None newValue is actually Optional<Element>.Some(Element(nilLiteral:())).

-DW

On Feb 6, 2016, at 4:19 PM, Dave via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Ah, ok, I think I understand your objection now. Let me try to summarize…

If we extend on MutableCollectionType like this:
subscript(ifExists idx: Self.Index) -> Self.Generator.Element? {
    get { return self.indices.contains(idx) ? self[idx] : nil as Self.Generator.Element? }
    set { if let nv = newValue where self.indices.contains(idx) { self[idx] = nv } }
}
then, when CollectionType.Generator.Element is NilLiteralConvertible, we have the following problem:
var array: [Int?] = [1]
array[ifExists: 0] = nil // *PROBLEM*: The most straight-forward way of safely setting something to nil silently fails because the compiler treats this as an Int?? instead of an Int?
print(array) // "[Optional(1)]"
array[ifExists: 0] = array[ifExists: 1] // *No problem*
print(array) // "[Optional(1)]"
array[ifExists: 0] = nil as Int? // *No problem*
print(array) // "[nil]"

But if we fix it to allow an unannotated nil to go through:
subscript(ifExists idx: Self.Index) -> Self.Generator.Element? {
    get { return self.indices.contains(idx) ? self[idx] : nil as Self.Generator.Element? }
    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)
                }
            }
        }
    }
}
then we have a different problem:
var array: [Int?] = [1, 2, 3]
array[ifExists: 0] = nil // *No problem*: The code no longer cares that this is an Int?? instead of an Int?
print(array) // "[nil, Optional(2), Optional(3)]"
array[ifExists: 1] = array[ifExists: 3] // *PROBLEM*: This shouldn’t do anything, but it succeeds because the code no longer cares that it’s an Int?? instead of an Int?
print(array) // "[nil, nil, Optional(3)]"
array[ifExists: 2] = nil as Int? // *No problem*
print(array) // "[nil, nil, nil]"

Assuming we’re all on the same page now… Yeah, as much as I’d love to keep the array[ifExists: 0] = nil behavior from the “fixed” version, I agree that the 2nd problem is clearly worse than the 1st, especially since being explicit about your nils (array[ifExists: 0] = nil as Int?) allows the assignment to go through.

So I’m in favor of the 1st one, which doesn’t allow unannotated nil assignments to succeed.

The only alternative I can think of is changing the language to allow subscripts to throw errors:
enum IndexError : ErrorType { case outOfRange }
subscript(throwing idx: Self.Index) throws -> Self.Generator.Element {
    get {
        guard self.indices.contains(idx) else { throw IndexError.outOfRange }
        return self[idx]
    }
    set {
        guard self.indices.contains(idx) else { throw IndexError.outOfRange }
        self[idx] = newValue
    }
}

Although… They aren’t mutually exclusive… It’s obviously not hard to imagine scenarios in which you don’t really care that you're out of bounds and just want to avoid crashing (otherwise we wouldn’t have been having this conversation in the first place) but nor is it hard to imagine scenarios in which you might want explicit confirmation that there wasn’t an error, rather than having to check for nil or compare before & after versions of the array. In those cases the throwing version would be appropriate. Plus, if your subscript function doesn’t only pass-through to an underlying collection, it might be handy to be able to throw, say, an OutOfMemory error if your subscript function loads or generates some large data structure. Or perhaps you’re writing a file manager, and you’re trying to get files by “subscripting” directories… The file may exist, but you might not have read permission.

Should we propose both?

- Dave Sweeris

On Feb 6, 2016, at 12:52, Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Probably I wasn't clear about that. Sorry.

My concern was about adding (code from Dave Sweeris):

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

Since it would allow this:

var array: [Int?] = [1]
array[ifExists: 0] = nil // sets array[0] to nil if index is valid
print(array) // "[nil]"
array = [1]
array[ifExists: 0] = array[ifExists: 1]
print(array) // "[nil]"

Whereas the normal behavior:

var array: [Int?] = [1]
array[ifExists: 0] = nil // does nothing
print(array) // "[1]"
array[ifExists: 0] = array[ifExists: 1] // does nothing
print(array) // "[1]"

Hope this clarifies my point
- Maximilian

Am 06.02.2016 um 21:31 schrieb Thorsten Seitz <tseitz42@icloud.com <mailto:tseitz42@icloud.com>>:

Am 06.02.2016 um 00:58 schrieb Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

You are totally right. The return type is "Int??".

My point was that if we allowed something like this (as suggested by Dave Sweeris I think):

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

To set the element at index 0 to nil instead of doing nothing.
The next example would also set index 0 to nil even though the getter failed:

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

No, it doesn't. Just try it out.

-Thorsten

- Maximilian

Am 05.02.2016 um 10:20 schrieb Haravikk <swift-evolution@haravikk.me <mailto:swift-evolution@haravikk.me>>:

On 4 Feb 2016, at 20:24, Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org <mailto: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.

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:

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

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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <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

Nice summary!

The first "problem" can be solved even easier!!! Due to a feature of Swift I just encountered:

  var num1: Int? = nil
  num1? = 1
  print(num1) // still nil
  
  num1 = 2
  print(num1) // Optional(2)
  num1? = 3
  print(num1) // Optional(3)
  num1? = nil // not allowed

If you put a "?" after the optional variable it only changes the variable if the variable is not nil. Assignments to "nil" are prohibited if "?" is used. So it is like a nil check and assignments happen only at the unwrapped type level ("Int" not "Int?"):

For "double Optionals":

  var num2: Int?? = nil
  num2? = 4
  print(num2) // still Optional(nil)
  
  num2 = .Some(nil)
  num2? = 5 // assigns; only one "unwrap"
  print(num2) // Optional(Optional(5))
  
  num2 = .Some(nil)
  num2?? = 6 // "double unwrap"
  print(num2) // still Optional(nil)

In our case we can "solve" the first problem the following way:

  var numbers: [Int] = [1, 2, 3]
  numbers[ifExists: 0]? = nil // not allowed
  
  var array: [Int?] = [1]
  array[ifExists: 0]? = nil // assignment is guaranteed to succeed if index exists
  print(array) // [nil]

Having subscripts which throw are good for catching "missing" indices. Although they require you to write "try" everywhere you use it and checking if the assignment comes through can also be done by unwrapping and optional assignment nil checks:

  if let value = array[ifExists: 0] {}
  // vs
  do { let value = try array[ifExists: 0] } catch {}
  
  if (array[ifExists: 0]? = 4) != nil {}
  // vs
  do { try array[ifExists: 0] = 4 } catch {}
  
  if let value = array[ifExists: 1] where (array[ifExists: 0]? = value) != nil {}
  // vs
  do { try array[ifExists: 0] = try array[ifExists: 1] } catch {}

Although I’m generally +1 for adding throwing subscripts but using them in this case doesn’t seem to be that useful.

- Maximilian

···

Am 07.02.2016 um 00:19 schrieb davesweeris@mac.com:

Ah, ok, I think I understand your objection now. Let me try to summarize…

If we extend on MutableCollectionType like this:
subscript(ifExists idx: Self.Index) -> Self.Generator.Element? {
    get { return self.indices.contains(idx) ? self[idx] : nil as Self.Generator.Element? }
    set { if let nv = newValue where self.indices.contains(idx) { self[idx] = nv } }
}
then, when CollectionType.Generator.Element is NilLiteralConvertible, we have the following problem:
var array: [Int?] = [1]
array[ifExists: 0] = nil // *PROBLEM*: The most straight-forward way of safely setting something to nil silently fails because the compiler treats this as an Int?? instead of an Int?
print(array) // "[Optional(1)]"
array[ifExists: 0] = array[ifExists: 1] // *No problem*
print(array) // "[Optional(1)]"
array[ifExists: 0] = nil as Int? // *No problem*
print(array) // "[nil]"

But if we fix it to allow an unannotated nil to go through:
subscript(ifExists idx: Self.Index) -> Self.Generator.Element? {
    get { return self.indices.contains(idx) ? self[idx] : nil as Self.Generator.Element? }
    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)
                }
            }
        }
    }
}
then we have a different problem:
var array: [Int?] = [1, 2, 3]
array[ifExists: 0] = nil // *No problem*: The code no longer cares that this is an Int?? instead of an Int?
print(array) // "[nil, Optional(2), Optional(3)]"
array[ifExists: 1] = array[ifExists: 3] // *PROBLEM*: This shouldn’t do anything, but it succeeds because the code no longer cares that it’s an Int?? instead of an Int?
print(array) // "[nil, nil, Optional(3)]"
array[ifExists: 2] = nil as Int? // *No problem*
print(array) // "[nil, nil, nil]"

Assuming we’re all on the same page now… Yeah, as much as I’d love to keep the array[ifExists: 0] = nil behavior from the “fixed” version, I agree that the 2nd problem is clearly worse than the 1st, especially since being explicit about your nils (array[ifExists: 0] = nil as Int?) allows the assignment to go through.

So I’m in favor of the 1st one, which doesn’t allow unannotated nil assignments to succeed.

The only alternative I can think of is changing the language to allow subscripts to throw errors:
enum IndexError : ErrorType { case outOfRange }
subscript(throwing idx: Self.Index) throws -> Self.Generator.Element {
    get {
        guard self.indices.contains(idx) else { throw IndexError.outOfRange }
        return self[idx]
    }
    set {
        guard self.indices.contains(idx) else { throw IndexError.outOfRange }
        self[idx] = newValue
    }
}

Although… They aren’t mutually exclusive… It’s obviously not hard to imagine scenarios in which you don’t really care that you're out of bounds and just want to avoid crashing (otherwise we wouldn’t have been having this conversation in the first place) but nor is it hard to imagine scenarios in which you might want explicit confirmation that there wasn’t an error, rather than having to check for nil or compare before & after versions of the array. In those cases the throwing version would be appropriate. Plus, if your subscript function doesn’t only pass-through to an underlying collection, it might be handy to be able to throw, say, an OutOfMemory error if your subscript function loads or generates some large data structure. Or perhaps you’re writing a file manager, and you’re trying to get files by “subscripting” directories… The file may exist, but you might not have read permission.

Should we propose both?

- Dave Sweeris

On Feb 6, 2016, at 12:52, Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Probably I wasn't clear about that. Sorry.

My concern was about adding (code from Dave Sweeris):

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

Since it would allow this:

var array: [Int?] = [1]
array[ifExists: 0] = nil // sets array[0] to nil if index is valid
print(array) // "[nil]"
array = [1]
array[ifExists: 0] = array[ifExists: 1]
print(array) // "[nil]"

Whereas the normal behavior:

var array: [Int?] = [1]
array[ifExists: 0] = nil // does nothing
print(array) // "[1]"
array[ifExists: 0] = array[ifExists: 1] // does nothing
print(array) // "[1]"

Hope this clarifies my point
- Maximilian

Am 06.02.2016 um 21:31 schrieb Thorsten Seitz <tseitz42@icloud.com <mailto:tseitz42@icloud.com>>:

Am 06.02.2016 um 00:58 schrieb Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

You are totally right. The return type is "Int??".

My point was that if we allowed something like this (as suggested by Dave Sweeris I think):

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

To set the element at index 0 to nil instead of doing nothing.
The next example would also set index 0 to nil even though the getter failed:

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

No, it doesn't. Just try it out.

-Thorsten

- Maximilian

Am 05.02.2016 um 10:20 schrieb Haravikk <swift-evolution@haravikk.me <mailto:swift-evolution@haravikk.me>>:

On 4 Feb 2016, at 20:24, Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org <mailto: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.

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:

_______________________________________________
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

In principle, I’m not opposed to that. In practice, though… unless you’re also proposing we give it “magic” syntax like what Optionals currently have, we’d have to manually wrap/unwrap the values:
bar[ifExists: 3] = Result.result(4)
print(bar[ifExists: 4].result)

Which would strictly speaking work, but IMHO is more annoying than just manually checking collection.indices.contains(idx) before attempting the subscript.

- Dave Sweeris

···

On Feb 6, 2016, at 13:21, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> wrote:

Ah ok, I was using the other version which distinguishes between nil and Optional(nil):

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! } }
    }
}

Where you can assign Optional(nil) to set a value to nil which is admittedly not very intuitive...

var array: [Int?] = [1]
array[ifExists: 0] = Optional(nil)
print(array) // "[nil]"
array = [1]
array[ifExists: 0] = array[ifExists: 1]
print(array) // "[Optional(1)]"

A solution would probably be to introduce another enum mimicking Optional but built for the array subscript which allows to distinguish both cases.

-Thorsten

Am 06.02.2016 um 21:52 schrieb Maximilian Hünenberger <m.huenenberger@me.com <mailto:m.huenenberger@me.com>>:

var array: [Int?] = [1]
array[ifExists: 0] = nil // sets array[0] to nil if index is valid
print(array) // "[nil]"
array = [1]
array[ifExists: 0] = array[ifExists: 1]
print(array) // "[nil]"

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