<html><head><meta http-equiv="Content-Type" content="text/html charset=utf-8"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class="">I believe the implementation of efficient in-place mutation is very explicit in the code -- it is done by implementing DictionaryValue’s subscript using a special “mutableAddressWithNativeOwner” addressor instead of a normal setter. </div><div class=""><br class=""></div><div class=""><a href="https://github.com/natecook1000/swift/blob/ed95aec4a20589a3b9c131f43444aa33705343cc/stdlib/public/core/HashedCollections.swift.gyb#L2169-L2173" class="">https://github.com/natecook1000/swift/blob/ed95aec4a20589a3b9c131f43444aa33705343cc/stdlib/public/core/HashedCollections.swift.gyb#L2169-L2173</a></div><div class=""><br class=""></div><div class="">AFAICU this would also work if DictionaryValue was a reference type. </div><div class=""><br class=""></div><div class="">Unfortunately, as far as I know, these addressors aren’t available outside stdlib, so custom collections cannot currently implement such mutable views (including mutable ranges) in a similarly efficient way. </div><div class=""><br class=""></div><div class="">We can, however, approximate a similar effect outside of stdlib by designing closure-based APIs like `mydict.withValues { values in values[i] = 42 }`, in which the collection <i class="">moves</i> its storage to the view while the closure is executing (temporarily making its own contents disappear / appear invalid). The syntax and underlying mental model is perhaps not as nice, but (assuming the compiler is able to optimize away the nonescaping closure) we can achieve some of the performance benefits. </div><br class=""><div><blockquote type="cite" class=""><div class="">On 2016-10-12, at 19:17, plx via swift-evolution <<a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a>> wrote:</div><br class="Apple-interchange-newline"><div class=""><meta http-equiv="Content-Type" content="text/html charset=utf-8" class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class="">Thanks for the quick reply; given that I’m quite wrong about the important mechanics I rescind my criticisms.<div class=""><br class=""></div><div class="">I will say I care about this enough to reply because the inability to do in-place mutation of dictionary values has been an incredibly frustrating limitation and I’d just assumed the situation with slices/views would necessarily have similar issues for similar reasons…but glad to learn it’s not what I thought!</div><div class=""><br class=""></div><div class="">That said, I think efficient in-place mutation is too important to only expose so implicitly (seemingly due to the compiler eliding the otherwise-expected retain increments when the view is sufficiently “transient”…which seems like you perhaps can’t have an "in-place capable" view that’s implemented as a class, I’d think).</div></div></div></blockquote><blockquote type="cite" class=""><div class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class=""><br class=""></div><div class="">But none of this impacts my change to being in support for the proposal.</div><div class=""><div class=""><div class=""><div class=""><div class=""><div class=""><br class=""><div class=""><blockquote type="cite" class=""><div class="">On Oct 12, 2016, at 10:07 AM, Nate Cook <<a href="mailto:natecook@gmail.com" class="">natecook@gmail.com</a>> wrote:</div><br class="Apple-interchange-newline"><div class=""><meta http-equiv="Content-Type" content="text/html charset=utf-8" class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><br class=""><div class=""><blockquote type="cite" class=""><div class="">On Oct 12, 2016, at 9:32 AM, plx via swift-evolution <<a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a>> wrote:</div><br class="Apple-interchange-newline"><div class=""><meta http-equiv="Content-Type" content="text/html charset=utf-8" class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class="">The issue addressed is real; I’m not sure this is the best approach. </div><div class=""><br class=""></div><div class="">In particular, unless I’m missing something obvious, the ownership strategy here would have to be:</div><div class=""><br class=""></div><div class=""><div class="">- `DictionaryKeys` and `DictionaryValues` would each induce the expected +1 retain on the underlying storage</div><div class="">- `DictionaryValues`’s mutations avoid triggering COW on the underlying storage by skipping the usual ownership check</div></div><div class=""><br class=""></div><div class="">…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?).</div></div></div></blockquote><div class=""><br class=""></div><div class="">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:</div><div class=""><br class=""></div></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;" class=""><div class=""><div class="">var a = Array(1...10)</div></div><div class=""><div class="">a[0..<5].reverse()</div></div><div class=""><div class="">a == [5, 4, 3, 2, 1, 6, 7, 8, 9, 10]</div></div></blockquote><div class=""><div class=""><br class=""></div><div class="">However, if you create a new variable out of the slice and reverse <i class="">that</i>, the slice does get its own storage:</div><div class=""><br class=""></div></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;" class=""><div class=""><div class="">var b = Array(1...10)</div></div><div class=""><div class="">var bSlice = b[0..<5]</div></div><div class=""><div class="">bSlice.reverse()</div></div><div class=""><div class="">bSlice == [5, 4, 3, 2, 1]</div></div><div class=""><div class="">b == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]</div></div></blockquote><div class=""><div class=""><br class=""></div><div class="">Strings and their views work the same way:</div><div class=""><br class=""></div></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;" class=""><div class=""><div class=""><div class="">var s = "abcdefg"</div></div></div><div class=""><div class=""><div class="">s.characters.append("H") // storage updated in place</div></div></div><div class=""><div class=""><div class="">s == "abcdefgH"</div></div></div><div class=""><div class=""><div class=""><br class=""></div></div></div><div class=""><div class=""><div class="">var sChars = s.characters // no copy yet</div></div></div><div class=""><div class=""><div class="">sChars.removeLast() // sChars gets its own copy before the mutation</div></div></div><div class=""><div class=""><div class="">s == "abcdefgH"</div></div></div><div class=""><div class=""><div class="">String(sChars) == "abcdefg"</div></div></div><div class=""><div class=""><div class=""><br class=""></div></div></div><div class=""><div class=""><div class=""><div class="">var t = s // no copy yet</div></div></div></div><div class=""><div class=""><div class=""><div class="">t.characters.removeLast() // t gets a new copy here</div></div></div></div><div class=""><div class=""><div class="">s == "abcdefgH"</div></div></div><div class=""><div class=""><div class="">t == "abcdefg"</div></div></div></blockquote><div class=""><br class=""></div><div class="">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.</div><br class=""><div class=""><blockquote type="cite" class=""><div class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class="">With that design, it seems like you’d wind up allowing things like the below:</div><div class=""><br class=""></div><div class=""> // example A</div><div class=""> let foo = [ “abc”: [1,2,3], “efg”: [4,5,6] ]</div><div class=""> let bar = foo // shared storage, no COW</div><div class=""> foo.values[foo.index(of: “abc”)!].append(789) // update shared storage, no COW</div><div class=""><br class=""></div><div class=""> // shared storage mutated,</div><div class=""> // despite (a) both being `let` and (b) only foo.values getting touched</div><div class=""> foo[“abc”] // [1, 2, 3, 789]</div><div class=""> bar[“abc”] // [1, 2, 3, 789]</div></div></div></blockquote><div class=""><br class=""></div><div class="">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.</div><br class=""><blockquote type="cite" class=""><div class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class=""><div class=""> // example B</div><div class=""> var foo = [ “abc”: [1,2,3], “efg”: [4,5,6] ]</div><div class=""> var bar = foo // shared storage, no COW</div><div class=""> foo.values[foo.index(of: “abc”)!].append(789)</div><div class=""><br class=""></div><div class=""> // shared storage mutated only foo.values getting touched</div><div class=""> foo[“abc”] // [1, 2, 3, 789]</div><div class=""> bar[“abc”] // [1, 2, 3, 789]</div></div></div></div></blockquote><div class=""><br class=""></div><div class="">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.</div><div class=""><br class=""></div><blockquote type="cite" class=""><div class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class=""><div class=""> // example C</div><div class=""> var foo = [ “abc”: [1,2,3], “efg”: [4,5,6] ]</div><div class=""> var bar = foo </div><div class=""> bar[“abc”] = [1, 2, 3, 4] // COW triggered here, no shared storage</div><div class=""> foo.values[foo.index(of: “abc”)!].append(789)</div><div class=""><br class=""></div><div class=""> // only `foo`’s storage mutated, b/c change to `bar` triggered COW</div><div class=""> foo[“abc”] // [1, 2, 3, 789]</div><div class=""> bar[“abc”] // [1, 2, 3, 4]</div></div></div></div></blockquote><div class=""><br class=""></div><div class="">This is the current behavior and would remain the same after the proposed the changes.</div><br class=""><blockquote type="cite" class=""><div class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class="">…where both A (by itself) and the B/C contrast seem very unwelcome.</div><div class=""><br class=""></div><div class="">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.</div><div class=""><br class=""></div><div class="">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).</div></div></div></blockquote><div class=""><br class=""></div>Nate</div><div class=""><br class=""></div></div></div></blockquote></div><br class=""></div></div></div></div></div></div></div>_______________________________________________<br class="">swift-evolution mailing list<br class=""><a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a><br class="">https://lists.swift.org/mailman/listinfo/swift-evolution<br class=""></div></blockquote></div><br class=""></body></html>