How to program (and teach programming) using value semantics

Starting a new thread to continue a discussion started here: Introducing Swift in higher education

It's API-decoupled through use of a name; you can always change the typealias definition later. Of course if you need to publish a binary-stable interface, i.e. you need it ABI-decoupled, then you would use something like a resilient struct with no public stored properties.

But almost nobody needs ABI decoupling in cases like this. Premature/inappropriate worry about coupling is one of the greatest sources of needless software complexity and inefficiency I've seen (I used the typealias for readability and abstraction reasons).

  • Is there any reason why you don't put friends in the Person struct? In practice, a Person may have many properties (e.g., tweets, likes, etc.). I think it's natural to put them in the struct?

It's hardly a fully worked-out design, so these things could change as more of it was written, but that decision was driven by the fact that I wanted an add(person) method and I wasn't ready to think about what should happen if that person to bring its own list of friends into the network, which list was not matched by incoming edges from those Persons already in the network, since friendship is a bidirectional relationship. I was going to let the SocialNetwork manage the invariant that the links always match up.

Another point that's probably more fundamental: Aggregation of values is a whole- part relationship and there's no whole-part relationship between a person and the half of the friendship relationship that we'd be storing in each participant in the relationship. That relationship is a logical part of the SocialNetwork.

That said, if friends is moved to the struct and suppose we would like to implement a method, say, getFriendNames() , for Person , then the method would need to take a closure to translate PersonID to name. This is a minor example why I said value type often required functional programming techniques.

“Functional programming” without the qualification “pure” doesn't have a very crisp definition, so when I hear the term I usually assume it means “purely functional programming.” It seems like here you're focusing on the higher-order programming aspects of FP. Your example is a pure query operation; there's not even a whiff of mutation about it, so of course it looks purely functional as well as higher-order.

But IMO your use of a closure here might be a premature decoupling. After all, in this context, there's only one thing such a closure could do: look up the name of the identified person in a captured SocialNetwork. You might as well just pass a SocialNetwork:

But then, at that point, maybe this doesn't really belong as a method on person, and then you really get nothing at all by storing the list of friends internally to the Person:

 extension SocialNetwork {
     func getFriendNames(of person: PersonID) -> [String] {
         return storage[person].friends.map { storage[$0].name }
     }
 }
1 Like

Photoshop is a counterexample. It runs responsively on an iPad and in your web browser as well as on the desktop, and handles image processing efficiently on documents as large as the surface of Mars resolved to one meter per pixel. Every editing operation in Photoshop is undoable, and it accomplishes that by, when each edit begins, storing a snapshot of the document state in the undo history. Of course it uses CoW to minimize the cost of storing these snapshots.

3 Likes

Indeed, and it was actually one of Sean Parent's older videos, Value Semantics and Concepts-based Polymorphism, that initially got me thinking a lot more about this topic.

Many of your forums posts, and older WWDC videos, have inspired me to try and rethink how I could program macOS and iOS apps in more idiomatic Swift, rather than just in an "Objective-C with Swift Syntax" way.

Interfacing with existing frameworks, like AppKit and UIKit, has proved the most challenging when trying to implement pure, top-to-bottom, value semantics. (And that's kind of where the last discussion got a bit into the weeds and maybe off-topic for this forum.)

Let me try and refocus the conversation in a follow-up comment.

1 Like

Thanks for your explanation.

On a second thought, that makes sense. I preferred to closure probably because I thought it was too heavyweight to pass the entire data model (although I knew there was COW).

That's an interesting point. I didn't think about it from this perspective. Will think more about this.

I remember we were told to avoid having too many references in our value types because of the cost of reference counting. Wouldn't having a value type with references to (in this case) many image tiles be a problem then? How do you work around that?

I believe the recommended solution to that is to wrap all the reference types in a final class and then CoW that class inside your struct. That way copying only needs to track the reference count of your class and not all of the individual references inside of it.

1 Like

oh yeah, I guess that works. Thanks!