Are value semantics really appropriate in a diagramming app?


(Rick M) #1

I watched the WWDC 2015 video about protocol-oriented programming and value semantics. Both of them use the example of a diagramming app, where the user can make a diagram of circles and polygons. They make a protocol for Drawable (makes sense), and then make Circle and Polygon structs.

I'm working on rewriting a long-term project of mine in Swift. It's a schematic capture CAD app, and it draws schematic diagrams built up from instances of parts from a library, and wires between them. Currently, my model is entirely made of classes, with reference semantics. I'm trying to see if it wouldn't make sense to make them structs with value semantics, but I don't think it does, and I'm looking for advice.

My model consists of the following objects: PartDefinition and PartInstance, as well as some geometric primitives. A PartDefinition has a set of properties (e.g. UUID, name, value), and a set of geometric primitives. The geometry defines the appearance on screen, and the properties give the part identity in the schematic. A PartInstance references a PartDefinition, and adds a position property (to define that instance's location in the canvas), as well as overriding zero or more of the PartDefinition's properties, or adding its own (for example, a PartInstance gets a Reference Designator, just a label for the instance, like "R1" or "U23"). A PartInstance rarely, if ever, overrides or adds geometry found in the corresponding PartDefinition.

The reason I don't think these work so well with value semantics is that they can be edited by the user: they can be repositioned, properties can be changed, etc. Even the geometry of a PartDefinition can be changed, and the intended result is that all instances displayed in the canvas reflect the new geometry.

Similarly, in the diagramming example from the WWDC videos, how would that app handle the user editing existing Drawables in the Diagram? Let's say you allow the user to click on a Drawable and drag it to another location in the canvas. Is this reasonable:

- User clicks with the mouse
- Find the item hit by that click by iterating through the array of Drawable
- var currentItem = self.items[hitIndex] (makes a copy)
- update currentItem with whatever changes have occurred
- self.items[hitIndex] = currentItem (copy the new values back into the array
- set window needs update

It seems like reference semantics are more appropriate here.

Thoughts? Thanks.

···

--
Rick Mann
rmann@latencyzero.com


(Rimantas Liubertas) #2

Similarly, in the diagramming example from the WWDC videos, how would that
app handle the user editing existing Drawables in the Diagram? Let's say
you allow the user to click on a Drawable and drag it to another location
in the canvas. Is this reasonable:

See this talk too: https://developer.apple.com/videos/play/wwdc2015/414/
It should answer a lot of your question and give some new ideas.

Best regards,
Rimantas


(Jens Alfke) #3

Yes, they are. (Just because structs exist doesn’t mean you have to use them everywhere.)

—Jens

···

On Aug 1, 2016, at 1:19 AM, Rick Mann via swift-users <swift-users@swift.org> wrote:

It seems like reference semantics are more appropriate here.


(Rick M) #4

I did. That's one of the two talks I mentioned.

···

On Aug 1, 2016, at 02:14 , Rimantas Liubertas <rimantas@gmail.com> wrote:

Similarly, in the diagramming example from the WWDC videos, how would that app handle the user editing existing Drawables in the Diagram? Let's say you allow the user to click on a Drawable and drag it to another location in the canvas. Is this reasonable:

See this talk too: https://developer.apple.com/videos/play/wwdc2015/414/
It should answer a lot of your question and give some new ideas.

--
Rick Mann
rmann@latencyzero.com


(Rick M) #5

That's the other problem, undo. Presumably, the copy-on-write semantics make this form of undo efficient, both in terms of making the copy for each operation, and in terms of the amount of memory required (because only fine-grained copies are made). But if the model is large and complicated, I'd be worried about the performance implications of this.

Maybe I'll try it, see how it works.

···

On Aug 1, 2016, at 02:25 , Rimantas Liubertas <rimantas@gmail.com> wrote:

I did. That's one of the two talks I mentioned.

Well, so they did cover this, didn't they? You move item, you get the new struct with the new position. And if you save the old one, you have a cheap way to implement undo.

--
Rick Mann
rmann@latencyzero.com


(Rick M) #6

Of course. I'm just trying to expand on the Diagramming app example in the talks to a more real-world example. They make a compelling case for a value-based model, but I'm currently stuck trying to figure out how to do this in my complex app without duplicating a lot of code.

···

On Aug 1, 2016, at 16:32 , Jens Alfke <jens@mooseyard.com> wrote:

On Aug 1, 2016, at 1:19 AM, Rick Mann via swift-users <swift-users@swift.org> wrote:

It seems like reference semantics are more appropriate here.

Yes, they are. (Just because structs exist doesn’t mean you have to use them everywhere.)

--
Rick Mann
rmann@latencyzero.com


(Jack Lawrence) #7

Jens: Why? There are significant benefits to value semantics for this type of problem, for the reasons laid out in the WWDC videos. It would be helpful to know why you disagree in this case—maybe there are solutions to the issues you’re thinking of.

Rick: I’d think that value semantics would be the right choice here. When you do a mutation, you would copy the state of the entire diagram. It should be efficient via COW, but if not you can implement you own more fine-grained COW types with isUniquelyReferenced(). This would allow you to easily support things like undo.

Jack

···

On Aug 1, 2016, at 4:32 PM, Jens Alfke via swift-users <swift-users@swift.org> wrote:

On Aug 1, 2016, at 1:19 AM, Rick Mann via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

It seems like reference semantics are more appropriate here.

Yes, they are. (Just because structs exist doesn’t mean you have to use them everywhere.)

—Jens
_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Rick M) #8

Jens: Why? There are significant benefits to value semantics for this type of problem, for the reasons laid out in the WWDC videos. It would be helpful to know why you disagree in this case—maybe there are solutions to the issues you’re thinking of.

Rick: I’d think that value semantics would be the right choice here. When you do a mutation, you would copy the state of the entire diagram. It should be efficient via COW, but if not you can implement you own more fine-grained COW types with isUniquelyReferenced(). This would allow you to easily support things like undo.

The more I consider this, the more I think value semantics won't work for me. I think, to take advantage of the easy undo feature, my entire model *must* be implemented with value semantics.

But my model has implicit reference semantics: multiple instances of a part can share a PartDefinition; it is intended that if the PartDefinition changes, all the referencing instances get the change. There are additional situations in which reference semantics are at play, as well: a PartDefinition can have one or more labels, but each instance can specify the relative location of the label for that instance. So, there is struct that contains a position and a reference to the label in the PartDefinition. But if the contents of the label changes, all the instances need to see that change.

I don't think I get to take advantage of value semantics, and it makes me wonder if any typical, non-trivial model's object graph really has no reference semantics.

···

On Aug 1, 2016, at 19:18 , Jack Lawrence <jackl@apple.com> wrote:

Jack

On Aug 1, 2016, at 4:32 PM, Jens Alfke via swift-users <swift-users@swift.org> wrote:

On Aug 1, 2016, at 1:19 AM, Rick Mann via swift-users <swift-users@swift.org> wrote:

It seems like reference semantics are more appropriate here.

Yes, they are. (Just because structs exist doesn’t mean you have to use them everywhere.)

—Jens
_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

--
Rick Mann
rmann@latencyzero.com


(Jordan Rose) #9

I happen to agree with Jens, though Dave Abrahams (and Crusty) might disagree with me. The number one consideration for choosing between a value type and a reference type is “do I want value semantics or reference semantics?” If I want to talk about a particular shape, talk about “that one”, I can only do that if there’s some notion of identity. So yes, if that’s part of my app, I would start with reference types.

Now, that doesn’t have to apply to rendering. As shown in the presentation, if you’re generating the shapes, there’s no need to refer to a particular shape afterwards…especially if the shapes are immutable. Any individual shape is just data to be rendered. But if I want a command like “change the color of that square”, then I need a way to know what “that square” refers to, and an object with persistent identity—a class instance—is one way to do it.

You can get a notion of “identity” in ways other than reference semantics—say, by generating unique IDs that go with a particular shape, and then looking them up later. But it’s a perfectly fine choice to make your model use reference semantics.

All of that said, reference semantics do have traps: mutation makes things harder to test in isolation, and mutation with shared state makes things not thread-safe. So you have to decide what trade-off you want to make in your app.

Jordan

···

On Aug 1, 2016, at 19:18, Jack Lawrence via swift-users <swift-users@swift.org> wrote:

Jens: Why? There are significant benefits to value semantics for this type of problem, for the reasons laid out in the WWDC videos. It would be helpful to know why you disagree in this case—maybe there are solutions to the issues you’re thinking of.

Rick: I’d think that value semantics would be the right choice here. When you do a mutation, you would copy the state of the entire diagram. It should be efficient via COW, but if not you can implement you own more fine-grained COW types with isUniquelyReferenced(). This would allow you to easily support things like undo.

Jack

On Aug 1, 2016, at 4:32 PM, Jens Alfke via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

On Aug 1, 2016, at 1:19 AM, Rick Mann via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

It seems like reference semantics are more appropriate here.

Yes, they are. (Just because structs exist doesn’t mean you have to use them everywhere.)

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

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


(Jens Alfke) #10

But my model has implicit reference semantics: multiple instances of a part can share a PartDefinition; it is intended that if the PartDefinition changes, all the referencing instances get the change.

Bingo. That’s the very definition of a reference.

···

On Aug 1, 2016, at 7:24 PM, Rick Mann <rmann@latencyzero.com> wrote:

On Aug 1, 2016, at 7:18 PM, Jack Lawrence <jackl@apple.com> wrote:

I’d think that value semantics would be the right choice here. When you do a mutation, you would copy the state of the entire diagram. It should be efficient via COW

It sounds like you’re talking about a persistent data structure <https://en.wikipedia.org/wiki/Persistent_data_structure>, which to me is not the same thing as a value type.

—Jens


(Dave Abrahams) #11

Jens: Why? There are significant benefits to value semantics for
this type of problem, for the reasons laid out in the WWDC
videos. It would be helpful to know why you disagree in this
case—maybe there are solutions to the issues you’re thinking of.

Rick: I’d think that value semantics would be the right choice
here. When you do a mutation, you would copy the state of the entire
diagram. It should be efficient via COW, but if not you can
implement you own more fine-grained COW types with
isUniquelyReferenced(). This would allow you to easily support
things like undo.

The more I consider this, the more I think value semantics won't work
for me. I think, to take advantage of the easy undo feature, my entire
model *must* be implemented with value semantics.

That certainly helps. You could introduce an explicit copy operation
to get around the use of classes, but that can get very messy.

But my model has implicit reference semantics: multiple instances of a
part can share a PartDefinition; it is intended that if the
PartDefinition changes, all the referencing instances get the
change.

That is definitely a reference. However, there are lots of ways to
represent references such that the entire model still has value
semantics. Reference semantics, in the broadest sense, are everywhere:
as soon as you have an array and an integer, you have reference
semantics. The problem with using classes is that they introduce
reference semantics *implicitly* and *prolifically*.

So, for example, if you are implementing a model and you want to
represent a selection as a separate data structure, then you'll need to
give every selectable element some kind of id, so you can store the ids
there. One way to do that is to store all your elements in an array in
your model, and use the index into the array as the id. Then when one
element needs to refer to another element, it stores the ID of that
element.

[Aside: one of the simplest and most efficient representations of a
generalized graph structure is `[[Int]]`, which has value semantics.
Each element of the outer array corresponds to a vertex, and each
element an inner array represents the target of that vertex's outgoing
edges]

The most obvious thing you don't get from this kind of arrangement is
automatic lifetime management: an element doesn't disappear just because
you've stopped referring to it. Whether that's appropriate for your
application or not is a question for you to answer.

You can selectively recreate as much of the implicit behavior of classes
as you like, e.g. storing reference counts to recreate automatic
lifetime management and/or threading a free list through the array to
maintain ID stability, but of course at some point it becomes silly.

Where your particular application falls in this spectrum is for you to
say. The fact that there's a component of reference semantics in your
model doesn't mean you can't use value types, and the fact that using
value types has some awesome benefits doesn't mean you can't use
classes. Weigh the tradeoffs and make an informed choice.

There are additional situations in which reference semantics are at
play, as well: a PartDefinition can have one or more labels, but each
instance can specify the relative location of the label for that
instance. So, there is struct that contains a position and a reference
to the label in the PartDefinition. But if the contents of the label
changes, all the instances need to see that change.

I don't think I get to take advantage of value semantics, and it makes
me wonder if any typical, non-trivial model's object graph really has
no reference semantics.

Some do, but many-to-one relationships are an important concept, and
many applications need to represent them somehow. I've personally found
that most such relationships are best expressed explicitly, which allows
me to preserve value semantics of the whole system. YMMV, of course.

HTH,

···

on Mon Aug 01 2016, Rick Mann <swift-users-AT-swift.org> wrote:

On Aug 1, 2016, at 19:18 , Jack Lawrence <jackl@apple.com> wrote:

--
-Dave


(Rick M) #12

Hmm. Seems there's a need in the language for expressing exactly this kind of thing: explicit reference semantics. I sure don't want to manage that all on my own.

···

On Aug 2, 2016, at 13:07 , Dave Abrahams via swift-users <swift-users@swift.org> wrote:

on Mon Aug 01 2016, Rick Mann <swift-users-AT-swift.org> wrote:

On Aug 1, 2016, at 19:18 , Jack Lawrence <jackl@apple.com> wrote:

Jens: Why? There are significant benefits to value semantics for
this type of problem, for the reasons laid out in the WWDC
videos. It would be helpful to know why you disagree in this
case—maybe there are solutions to the issues you’re thinking of.

Rick: I’d think that value semantics would be the right choice
here. When you do a mutation, you would copy the state of the entire
diagram. It should be efficient via COW, but if not you can
implement you own more fine-grained COW types with
isUniquelyReferenced(). This would allow you to easily support
things like undo.

The more I consider this, the more I think value semantics won't work
for me. I think, to take advantage of the easy undo feature, my entire
model *must* be implemented with value semantics.

That certainly helps. You could introduce an explicit copy operation
to get around the use of classes, but that can get very messy.

But my model has implicit reference semantics: multiple instances of a
part can share a PartDefinition; it is intended that if the
PartDefinition changes, all the referencing instances get the
change.

That is definitely a reference. However, there are lots of ways to
represent references such that the entire model still has value
semantics. Reference semantics, in the broadest sense, are everywhere:
as soon as you have an array and an integer, you have reference
semantics. The problem with using classes is that they introduce
reference semantics *implicitly* and *prolifically*.

So, for example, if you are implementing a model and you want to
represent a selection as a separate data structure, then you'll need to
give every selectable element some kind of id, so you can store the ids
there. One way to do that is to store all your elements in an array in
your model, and use the index into the array as the id. Then when one
element needs to refer to another element, it stores the ID of that
element.

[Aside: one of the simplest and most efficient representations of a
generalized graph structure is `[[Int]]`, which has value semantics.
Each element of the outer array corresponds to a vertex, and each
element an inner array represents the target of that vertex's outgoing
edges]

The most obvious thing you don't get from this kind of arrangement is
automatic lifetime management: an element doesn't disappear just because
you've stopped referring to it. Whether that's appropriate for your
application or not is a question for you to answer.

You can selectively recreate as much of the implicit behavior of classes
as you like, e.g. storing reference counts to recreate automatic
lifetime management and/or threading a free list through the array to
maintain ID stability, but of course at some point it becomes silly.

Where your particular application falls in this spectrum is for you to
say. The fact that there's a component of reference semantics in your
model doesn't mean you can't use value types, and the fact that using
value types has some awesome benefits doesn't mean you can't use
classes. Weigh the tradeoffs and make an informed choice.

There are additional situations in which reference semantics are at
play, as well: a PartDefinition can have one or more labels, but each
instance can specify the relative location of the label for that
instance. So, there is struct that contains a position and a reference
to the label in the PartDefinition. But if the contents of the label
changes, all the instances need to see that change.

I don't think I get to take advantage of value semantics, and it makes
me wonder if any typical, non-trivial model's object graph really has
no reference semantics.

Some do, but many-to-one relationships are an important concept, and
many applications need to represent them somehow. I've personally found
that most such relationships are best expressed explicitly, which allows
me to preserve value semantics of the whole system. YMMV, of course.

--
Rick Mann
rmann@latencyzero.com


(Dave Abrahams) #13

Jens: Why? There are significant benefits to value semantics for
this type of problem, for the reasons laid out in the WWDC
videos. It would be helpful to know why you disagree in this
case—maybe there are solutions to the issues you’re thinking of.

Rick: I’d think that value semantics would be the right choice
here. When you do a mutation, you would copy the state of the entire
diagram. It should be efficient via COW, but if not you can
implement you own more fine-grained COW types with
isUniquelyReferenced(). This would allow you to easily support
things like undo.

The more I consider this, the more I think value semantics won't work
for me. I think, to take advantage of the easy undo feature, my entire
model *must* be implemented with value semantics.

That certainly helps. You could introduce an explicit copy operation
to get around the use of classes, but that can get very messy.

But my model has implicit reference semantics: multiple instances of a
part can share a PartDefinition; it is intended that if the
PartDefinition changes, all the referencing instances get the
change.

That is definitely a reference. However, there are lots of ways to
represent references such that the entire model still has value
semantics. Reference semantics, in the broadest sense, are everywhere:
as soon as you have an array and an integer, you have reference
semantics. The problem with using classes is that they introduce
reference semantics *implicitly* and *prolifically*.

So, for example, if you are implementing a model and you want to
represent a selection as a separate data structure, then you'll need to
give every selectable element some kind of id, so you can store the ids
there. One way to do that is to store all your elements in an array in
your model, and use the index into the array as the id. Then when one
element needs to refer to another element, it stores the ID of that
element.

[Aside: one of the simplest and most efficient representations of a
generalized graph structure is `[[Int]]`, which has value semantics.
Each element of the outer array corresponds to a vertex, and each
element an inner array represents the target of that vertex's outgoing
edges]

The most obvious thing you don't get from this kind of arrangement is
automatic lifetime management: an element doesn't disappear just because
you've stopped referring to it. Whether that's appropriate for your
application or not is a question for you to answer.

You can selectively recreate as much of the implicit behavior of classes
as you like, e.g. storing reference counts to recreate automatic
lifetime management and/or threading a free list through the array to
maintain ID stability, but of course at some point it becomes silly.

Where your particular application falls in this spectrum is for you to
say. The fact that there's a component of reference semantics in your
model doesn't mean you can't use value types, and the fact that using
value types has some awesome benefits doesn't mean you can't use
classes. Weigh the tradeoffs and make an informed choice.

There are additional situations in which reference semantics are at
play, as well: a PartDefinition can have one or more labels, but each
instance can specify the relative location of the label for that
instance. So, there is struct that contains a position and a reference
to the label in the PartDefinition. But if the contents of the label
changes, all the instances need to see that change.

I don't think I get to take advantage of value semantics, and it makes
me wonder if any typical, non-trivial model's object graph really has
no reference semantics.

Some do, but many-to-one relationships are an important concept, and
many applications need to represent them somehow. I've personally found
that most such relationships are best expressed explicitly, which allows
me to preserve value semantics of the whole system. YMMV, of course.

Hmm. Seems there's a need in the language for expressing exactly this
kind of thing: explicit reference semantics.

That's called “class.” :wink: But more seriously, if you have ideas, I'm
all ears (on the -evolution list, of course).

I sure don't want to manage that all on my own.

It's not that big a deal; you manage it all on your own every time you
use an array, dictionary, or set, with indices or keys playing the role
of references.

Cheers,

···

on Tue Aug 02 2016, Rick Mann <rmann-AT-latencyzero.com> wrote:

On Aug 2, 2016, at 13:07 , Dave Abrahams via swift-users <swift-users@swift.org> wrote:
on Mon Aug 01 2016, Rick Mann <swift-users-AT-swift.org> wrote:

On Aug 1, 2016, at 19:18 , Jack Lawrence <jackl@apple.com> wrote:

--
-Dave