Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to implement a bidirectional relationship between two structs using only constant properties?

My use case is based on the following model:

struct Person {
  let name: String
  let houses: [House]
}

struct House {
  let owner: Person
}

Now, ideally I would like to maintain a bidirectional relationship that requires every house to have exactly one owner where an owner should also know all its houses.

Using the above data structures, is it possible to create instances of House and Person such that there is a relationship between the two and the objects are essentially pointing at each other?

I guess the phrasing of this already is somewhat misleading, because due to the value semantics of structs, they don't really point anywhere but are only holding copies of values. It seems to be like it should be obvious that it's not possible to create these two objects with a bidirectional relationship, but I still wanted to be sure and ask this questions here!

An obvious solution would also be to make houses and owner variables using var instead of let when declaring them, then the relationship could be maintained in the initializer of each struct:

struct Person {
  let name: String
  var houses: [House]

  init(name: String, houses: [House]) {
    self.name = name
    self.houses = houses
    self.houses = houses.map { (house: House) in
      var newHouse = house
      newHouse.owner = self
      return newHouse
    }
  }
}


struct House {
  var owner: Person

  init(owner: Person) {
    self.owner = owner
    var newHouses = self.owner.houses
    newHouses.append(self)
    self.owner = Person(name: owner.name, houses: newHouses)
  }
}   

However, what if I want to keep houses and owner constant? As I said, it seems to be obvious that it's not possible, but I'm wondering if there's some fancy (maybe functional) way to achieve this? I was thinking about lenses, which can be used as getters and setters when dealing with immutable models.

like image 951
nburk Avatar asked Jul 25 '16 01:07

nburk


2 Answers

What you're describing sounds more like an ORM than a language feature, and also doesn't sound appropriate to handle with value types like structs. How do you expect the language to know that owner is the inverse relationship property of houses and needs to be maintained accordingly? This is something that needs to be enforced with code is not possible with the Swift language alone.

It sounds like you're trying to maintain some kind of model object graph, which is a problem that's been solved many times over. You should take a look at Realm DB and Core Data both of which offer managed bidirectional relationships. You'll find however that neither of these are implemented with structs. The problem with using value types is that they are copy-on-write. As soon as you mutate one struct in the object graph, you'd need to reassign all of it's related things to the new, mutated copy, which then in turn are mutated and would need to update all of their related structs. Reference semantics (classes) make maintaining an object graph much easier as mutations don't produce new instances.

like image 94
Patrick Goley Avatar answered Sep 26 '22 01:09

Patrick Goley


What you want is not possible using structs. Your current solution (with var) doesn't work like you think it does: all values are independent copies. Consider the following example:

let p = Person(name: "Nick", houses: [])
let h = House(owner: p)
h.owner.houses
p.houses

Because each copy is independent, this means p isn't the same as h.owner.

If you need a bidirectional relationship, you should use objects with weak pointers (to break the reference cycle). Alternatively, you could think to see if you need the bidirectional relationship.

like image 26
Chris Eidhof Avatar answered Sep 24 '22 01:09

Chris Eidhof