[swift-evolution] Optional safe subscripting for arrays
davesweeris at mac.com
davesweeris at mac.com
Sat Feb 6 00:01:34 CST 2016
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 at 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 at swift.org <mailto:swift-evolution at 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 at me.com <mailto:m.huenenberger at 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 at swift.org <mailto:swift-evolution at swift.org>>:
>>>
>>>> All right, I put together a proposal:
>>>>
>>>> https://github.com/salutis/swift-evolution/blob/master/proposals/XXXX-runtime-safe-array-subscripting.md <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 <https://github.com/apple/swift-evolution/pull/133>
>>>>
>>>> Let’s see how this goes.
>>>>
>>>> R+
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160205/6ec49b9c/attachment.html>
More information about the swift-evolution
mailing list