Circular references with vals in Kotlin
Asked Answered
B

4

5

In Kotlin, say I have data class A (val f: B) and data class B (val f: A). I want to initialize local var a: A and var b: B such that a.f is b and b.f is a. A.f and B.f must remain vals. Is this circular instantiation possible?

data class A(val f: B)

data class B(val f: A)

fun foo() {
    var a: A
    var b: B
    // instantiate a and b here with a.f == b and b.f == a ??
}
Bellona answered 16/2, 2016 at 17:40 Comment(0)
A
5

Not exactly what you want but should work:

interface A {
  val f: B
}

interface B {
  val f: A
}

data class AImpl(override var f: B) : A

data class BImpl(override var f: A) : B

fun <T> uninitialized(): T = null as T

fun foo() {
  var aImpl = AImpl(uninitialized())
  var bImpl = BImpl(aImpl)
  aImpl.f = bImpl
  val a: A = aImpl
  val b: B = bImpl
}

If you do not care about data class properties being vals, you can get rid of interfaces and use just implementation classes.

Ashworth answered 16/2, 2016 at 17:54 Comment(0)
B
4

Answered by Dmitry Jemerov on the kotlinlang Slack group:

The only possibility is to use reflection. Pass a dummy B instance to the constructor of A, then create a real instance of B passing the A instance as a parameter, and then use reflection to change the value of “f” field in the A instance to the B instance.

But I would strongly suggest you to not do that, and instead to reconsider your data model.

Bellona answered 16/2, 2016 at 17:40 Comment(0)
D
1

Passing an object which is not constructed yet as argument seems to be impossible. So, I think such cross-reference initialization is not possible with original data classes.

But some workaround could be done though:

data class A(val f: A.() -> B)
data class B(val f: B.() -> A)

val A.b: B get() = f(this)
val B.a: A get() = f(this)

fun test() {
    val O = object {
        val refA: A = A { refB }
        val refB: B = B { refA }
    }

    var a = O.refA
    var b = O.refB

    // validating cross-refs
    require( a === b.a )
    require( b === a.b )
    require( b === b.a.b )
    require( a === a.b.a )

    println("Done.")
}
Diamond answered 16/2, 2016 at 20:38 Comment(0)
M
1

You can do it if you explicitly declare your self-referential val as Lazy:

sealed class MyData {
    data class A(val x: Int) : MyData()
    data class B(val x : Int, val rb: Lazy<MyData>) : MyData() {
        val r: MyData by rb
    }
}

fun <A : Any> rec(body: (Lazy<A>) -> A): A {
    lateinit var a: A
    a = body(lazy { a })
    return a
}

fun MyData.print(gas: Int): String = if (gas <= 0) "..." else
    when(this) {
        is MyData.A -> "A(x=$x)"
        is MyData.B -> {
            val rbString = 
                if (rb.isInitialized()) 
                    r.print(gas - 1)
                else 
                    "<thunk>"
            "B(x=$x, rb=$rbString)" 
        }
    }

fun main() {
    val a = MyData.A(42)
    val b1 = MyData.B(1, lazy { a })
    println(b1.r) // Force value
    println(b1)
    val b2 = rec<MyData.B> { b2 -> MyData.B(1, b2) }
    println(b2.r.print(4))
}

This prints

A(x=42)
B(x=1, rb=A(x=42))
B(x=1, rb=B(x=1, rb=B(x=1, rb=B(x=1, rb=...))))
Matri answered 19/2, 2020 at 10:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.