I have been using an identifier type like the following. I use it in more complex scenarios (e.g., generic struct, protocol, etc) and it works fine.
struct Identifier<Value>: Hashable, Codable {
let uuid: UUID = UUID()
}
struct Foo {
let id: Identifier<Foo>
// ...
}
However, I find I always have difficulty in understanding it on concept level, because there are mutual dependence between the two types in the above example: Foo
<--> Identifier<Foo>
.
I used to do a lot of OOP programing and I never thought too much about cyclic dependence between classes, because in my opinion it's typical in OOP. I mainly use value types nowadays and I'm aware that I might introduce mutual dependency unconsciously in value type programming too, like the following. However, for unclear reason I don't think too much about it either.
struct Foo {
var x: Int
// Foo references Bar here.
var y: Bar
}
struct Bar {
var a: Int
// Bar's method parameter references Foo.
func doSomething(foo: Foo) {
// ...
}
}
But I always feel a bit uneasy when I use the above identifier type, although I know it will just work. Below is another (a bit more complex) example when I refactoring my code today.
First, the initial code are straightforward:
// MARK: Foo
struct FooEditableProperties {
var y: Int
}
struct Foo {
var x: Int
var y: Int
mutating func modify(props: FooEditableProperties) {
y = props.y
}
}
// MARK: Bar
struct BarEditableProperties {
var y: String
}
struct Bar {
var x: String
var y: String
mutating func modify(props: BarEditableProperties) {
y = props.y
}
}
In my code I put Foo
and Bar
in seprate arrays. I extend array to add custom APIs and I'd like to share the code for the two arrays. That means I need general types (either protocol or generic struct) to represent Foo
and Bar
, FooEditableProperties
and BarEditableProperties
, respectively.
The best way in this case is to use generic struct for both, as below:
struct EditableProperties<Value> {
var y: Value
}
struct Entity<Value> {
var x: Value
var y: Value
mutating func modify(props: EditableProperties<Value>) {
y = props.y
}
}
Just for dicussion purpose (BTW, it's actually true in my real code), let me suppose I don't want to use generic type for Foo
and Bar
, the code will be like the following:
protocol EntityProtocol {
associatedtype Value
var x: Value { get set }
var y: Value { get set }
}
struct EditableProperties<EntityProtocol> {
var y: Int
}
// MARK: Foo
struct Foo: EntityProtocol {
var x: Int
var y: Int
mutating func modify(props: EditableProperties<Foo>) {
y = props.y
}
}
The code works fine. But again I have some difficulty in understanding
the relation between the types. I mean, the relation is not as straightforward as the original code. Actually I'm not even sure if there is cyclic dependence in this example. If we think Foo
depends on EditableProperties
, and EditableProperties
depends on EntityProtocol
. That looks simple. But if we think Foo
depends on EditableProperties<Foo>
, then we have cyclic dependence: Foo
-> EditableProperties<Foo>
-> Foo
.
While my main purpose of the question is to look for a simple way to understand cyclic relations on concept level, I suspect my confusion is also because I don't know how compiler handles these situations internally. I wonder how you guys think about it? Should cyclic dependency among types be avoided unless it's really necessary, or is it just normal?