Property delegates for bidirectional relationships

Sometimes a bidirectional relationship between objects is helpful, but maintaining the consistency manually is verbose and error-prone. This seems to be a good use-case for property delegation. Is the following something for the standard lib?

Bidirectional relationships come in three flavors.

  • one-to-one
  • one-to-many / many-to-one
  • many-to-many

Let’s consider the first first:

class OneToOne<T, V>(
    private val prop: V.() -> KMutableProperty0<T?>
) : ReadWriteProperty<T, V?> {
    private var ref: V? = null

    override fun getValue(thisRef: T, property: KProperty<*>): V? = ref

    override fun setValue(thisRef: T, property: KProperty<*>, value: V?) {
        if (ref === value) {
            return
        }
        val old = ref
        ref = value
        if (old !== null) {
            old.prop().set(null)
        }
        if (value !== null) {
            value.prop().set(thisRef)
        }
    }
}

The usage is short and compact:

class A {
    var b: B? by OneToOne { ::a }
}

class B {
    var a: A? by OneToOne { ::b }
}

And it works like this:

fun main() {
    val a1 = A()
    val a2 = A()
    val b1 = B()
    val b2 = B()
    a1.b = b1
    check(a1.b === b1)
    check(b1.a === a1)
    a2.b = b2
    check(a2.b === b2)
    check(b2.a === a2)
    a1.b = b2
    check(a1.b === b2)
    check(b1.a === null)
    check(a2.b === null)
    check(b2.a === a1)
}

For many-to-one and many-to-many relationships, similar property delegate providers can be written, which are used like this:

class Parent {
    val children: MutableList<Child> by ManyToOne { ::parent }
}
class Child {
    var parent: Parent? by OneToMany { ::children }
}

class X {
    val ys: MutableList<Y> by ManyToMany { ::xs }
}
class Y {
    val xs: MutableList<X> by ManyToMany { ::ys }
}
1 Like

Is the following something for the standard lib?

Cyclic reference structure is very often an anti-pattern.
Mutable cyclic reference structure is almost always an anti-pattern.

Standard library? I don’t think so.

1 Like

Also

  • XXXToMany implies O(N) updates. O(N) sounds too much for a simple property assigment.
  • Thread-safety? Passing mutables references between threads - sounds like a nightmare.
    If you are not making this thread-safe then you are basically forbidding to pass such objects to any multithreaded code.
3 Likes

It seems you can achieve the same behaviour as the 1:1 by passing the same object “ReferenceContainer” to both A and B.

And “ReferenceContainer” contains a mutable reference.

I think the main point is that you can change A-B assignments at any time.

Re-reading the post I admit I didn’t understand it.

So the 1:1 is to create a “mutable” circular relationship between 2 objects. I’m not sure that’s a common pattern.

Ok, does not seem to be very popular. At least it was a nice exercise. Thanks for the feedback.