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?