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 thePerson
struct? In practice, aPerson
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 Person
s 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()
, forPerson
, then the method would need to take a closure to translatePersonID
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 }
}
}