Sorry, sometimes I forget how deeply reference-semantic thinking is ingrained in the culture…
When you store an instance of value-semantic Node
, you're storing (a copy of) its value, just like when you store an Int
, you're storing a copy of its value. You're not surprised that if you store the x
coordinate of a Point
, you can't magically reach back into the Point
and mutate it just by changing your stored Int
, are you? So, no, you don't get to do that with a value semantic type. Storing a copy of the Node
locally is of no use to you in affecting a mutation on some other Node
; you really have no reason to be storing a copy of it unless you have some use for a snapshot of its whole value, which includes the values of all its children.
If I take my trivial example and bring it into a "real world" application, then I routinely find myself with views that have properties referencing some shared state.
Forum friendly examples are always tough, but maybe this will help a bit:
Thinking in AppKit/UIKit as SwiftUI is quite different.
Ah, yeah… part of the whole point of SwiftUI was to move away from the rampant reference semantics that pervades the Cocoa kits.
final class Model {
var tree: Tree {
you've already left the world of value semantics as soon as you have a class with a mutable member. If you want to stay within value semantics, make your model a struct
. Or in your case since there's nothing in it but a Tree
, you could
typealias Model = Tree
Something has to own the model; that's the one thing with a stored Model
property. I'm not an expert on the Cocoa kits, but I'm guessing if the model is your document, then probably the Application
class (or its delegate) should own it, and if this is more like a representation of the state of some view, maybe the ViewController
(or its delegate) is the right owner.
In this example, a mutation of the tree
in the Model
will trigger a CoW because ListView
and SummaryView
each a reference to it.
Well, no, they each have a copy of it, and thinking of them as references will get you in big trouble. But yes, if you store copies of arrays and then mutate them, you should expect to pay for it. These are really copies (which is why you can't use them to mutate one another), and CoW is just a way of postponing (most of) the cost of making them… possibly forever, if you never mutate them.
A mutation of any property in the DetailView
needs to propagate back to the Model
and then down to the views.
Nope. I don't mean to be flip, but that's just not the way to think about these values.
Now, it may be that the Cocoa kits are really hostile to value semantics; they are, after all, based on an “everything is a reference” programming model. For example, maybe there's a method like this that you need to override in order to affect any interaction at all from your DetailView
:
func onTap()
There's no way for this method to be passed an inout Node
for mutation. Okay, then you need to make some compromises. Maybe you bring back the Model
class and spread references to it around the views, but the views store keypaths into the model that describe the “address” of the specific node to which they correspond. At least then the Law of Exclusivity would dynamically ensure no overlapping accesses to the forest, and below that level you'd have static guarantees from value semantics. Such a keypath would be equivalent to an [Int]
storing the child indices in a path from the root of the model. But as I understand it, the best practice for keeping everything in sync is to rebuild/adjust the view hierarchy to match the model, using one consistent procedure, after each mutation… so you would not actually expect to ever read the model through these stored references. I learned about the consistent rebuild from @kylemacomber, who knows a lot more about how to develop GUI apps using the Cocoa kits than I probably ever will, so I hope he will chime in with details.
Now, there's a strong argument to be made that perhaps the Views
should only have access to the Model
.
I'm no expert, but I think MVC is supposed to isolate views from models?
the Model
is likely to be a reference type so now all we've really done is hide the value type behind a reference type.
That is sometimes a necessary compromise, but it still offers some protection that you don't get by making a fine-grained network of interacting objects.
Not if it's truly a tree, and you don't need to traverse from child to parent or across siblings. That's perfectly well representable by a simple value because of the whole-part relationships I mentioned earlier. I was describing what you do for traversing an arbitrary graph. Then (among other approaches) you can give each vertex an integer index and represent all of the edges in the graph as [[Int]]
.
You have to expunge that from your thinking and your language if you want to deal effectively with value types directly. There is no "reference to a value." If Tree
is a value type, there is no reference-to-a-Tree
.