[swift-evolution] [Proposal Draft] Provide Custom Collections for Dictionary Keys and Values
Nate Cook
natecook at gmail.com
Fri Oct 14 01:35:41 CDT 2016
> On Oct 13, 2016, at 1:28 AM, Dave Abrahams via swift-evolution <swift-evolution at swift.org> wrote:
>
> on Wed Oct 12 2016, Nate Cook <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>
>>> On Oct 12, 2016, at 9:32 AM, plx via swift-evolution <swift-evolution at swift.org> wrote:
>>>
>>> The issue addressed is real; I’m not sure this is the best approach.
>>>
>>> In particular, unless I’m missing something obvious, the ownership strategy here would have to be:
>>>
>>> - `DictionaryKeys` and `DictionaryValues` would each induce the expected +1 retain on the
>> underlying storage
>>> - `DictionaryValues`’s mutations avoid triggering COW on the underlying storage by skipping the
>> usual ownership check
>>>
>>> …as otherwise it’s unclear how you’d do those in-place mutations
>>> (and this seems to be how the implementation works...is that
>>> correct?).
>>
>> That's not quite right—when you access these views through the
>> dictionary, they do not increment the storage retain count. This is
>> the way slicing and views currently work on other mutable types. For
>> example, when you reverse a slice of an array in-place, the slice
>> doesn't get its own duplicate storage:
>>
>> var a = Array(1...10)
>> a[0..<5].reverse()
>> a == [5, 4, 3, 2, 1, 6, 7, 8, 9, 10]
>
> Oh, yes it certainly does. This is currently inefficient because
> pinning is tied to addressors and addressors can only return pointers to
> things that actually exist in memory somewhere. The slice doesn't.
Ack, sorry everyone! Listen to Dave.
I got carried away with examples that went further than my proposal. As far as I can tell from my testing, the examples in the proposal are still accurate.
>> However, if you create a new variable out of the slice and reverse that, the slice does get its own
>> storage:
>>
>> var b = Array(1...10)
>> var bSlice = b[0..<5]
>> bSlice.reverse()
>> bSlice == [5, 4, 3, 2, 1]
>> b == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>
> This doesn't demonstrate anything; you're just showing the effects of
> value semantics. If you go
>
> b[0..<5] = bSlice
>
> you'll then have
>
> b == [5, 4, 3, 2, 1, 6, 7, 8, 9, 10]
>
> And that is exactly what happens in the first example.
>
> This is a major flaw that prevents Swift's collection model from being
> fully general and efficient. I think we will be fixing it, by fixing
> the way inout works, as a consequence of the work on ownership, for
> Swift 4. But as noted elsewhere, that's a wild-ass guess at the moment.
>
>> Strings and their views work the same way:
>>
>> var s = "abcdefg"
>> s.characters.append("H") // storage updated in place
>> s == "abcdefgH"
>>
>> var sChars = s.characters // no copy yet
>> sChars.removeLast() // sChars gets its own copy before the mutation
>> s == "abcdefgH"
>> String(sChars) == "abcdefg"
>>
>> var t = s // no copy yet
>> t.characters.removeLast() // t gets a new copy here
>> s == "abcdefgH"
>> t == "abcdefg"
>>
>> I don't know the name of the compiler feature that enables this, but
>> it's a critical part of the way views and slices work.
>
> I wish :-)
😳
>>> With that design, it seems like you’d wind up allowing things like the below:
>>>
>>> // example A
>>> let foo = [ “abc”: [1,2,3], “efg”: [4,5,6] ]
>>> let bar = foo // shared storage, no COW
>>> foo.values[foo.index(of: “abc”)!].append(789) // update shared storage, no COW
>>>
>>> // shared storage mutated,
>>> // despite (a) both being `let` and (b) only foo.values getting touched
>>> foo[“abc”] // [1, 2, 3, 789]
>>> bar[“abc”] // [1, 2, 3, 789]
>>
>> Example A isn't allowed—if foo and bar are both immutable, both of
>> their `values` collections are also immutable, so there's no way to
>> modify their shared storage.
>>
>>> // example B
>>> var foo = [ “abc”: [1,2,3], “efg”: [4,5,6] ]
>>> var bar = foo // shared storage, no COW
>>> foo.values[foo.index(of: “abc”)!].append(789)
>>>
>>> // shared storage mutated only foo.values getting touched
>>> foo[“abc”] // [1, 2, 3, 789]
>>> bar[“abc”] // [1, 2, 3, 789]
>>
>> Example B is incorrect—the mutation at `foo.values[...].append(789)`
>> triggers a copy of the entire dictionary's underlying storage before
>> allowing the mutation, since it knows that storage isn't uniquely
>> referenced.
>>
>>> // example C
>>> var foo = [ “abc”: [1,2,3], “efg”: [4,5,6] ]
>>> var bar = foo
>>> bar[“abc”] = [1, 2, 3, 4] // COW triggered here, no shared storage
>>> foo.values[foo.index(of: “abc”)!].append(789)
>>>
>>> // only `foo`’s storage mutated, b/c change to `bar` triggered COW
>>> foo[“abc”] // [1, 2, 3, 789]
>>> bar[“abc”] // [1, 2, 3, 4]
>>
>> This is the current behavior and would remain the same after the proposed the changes.
>>
>>> …where both A (by itself) and the B/C contrast seem very unwelcome.
>>>
>>> Also, even if we assume we only ever make *responsible* use, having
>>> the stdlib include such directly-mutating views would seem likely to
>>> complicate any future concurrency plans.
>>>
>>> To reiterate, I think the issue being addressed here is extremely
>>> important…I just don’t think I can get behind this type of solution
>>> (unless I’m grossly misunderstanding its mechanics).
>>
>> Nate
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>
> --
> -Dave
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20161014/dc62b1d8/attachment.html>
More information about the swift-evolution
mailing list