this is a really basic stupid question but at wor...
# server
f
this is a really basic stupid question but at work we have the notion of orders with checks
Copy code
data class Order(
    val orderId: String,
    ...
    val check: Check
)
Copy code
data class Check(
    val checkId: String,
    ...
)
we'd like them to be able to reference each other 🧵
Copy code
data class Order(
    val orderId: String,
    ...
    val check: Check
)
Copy code
data class Check(
    val order: Order, //parent
    val checkId: String,
    ...
)
but then we have a circular reference, and we can't instantiate them
Copy code
val myOrder = MyOrder(
    orderId = "1",
    foo = "foo",
    check = MyCheck(
        checkId = "2",
        bar = "bar",
        order = myOrder //doesn't compile
    )
)
we can mark the reference attributes as lateinit var, but then they object are mutable and we don't want that 😕 is there a way to solve this? I guess we could cheat and like use Jackson to instantiate them from json string representations, but... that's not ideal
e
it is possible to
Copy code
class Order {
    val orderId: String
    val check: Check

    constructor(orderId: String, check: Check) {
        this.orderId = orderId
        this.check = check
    }

    constructor(orderId: String, checkId: String) {
        this.orderId = orderId
        check = Check(checkId = checkId, order = this)
    }

    override fun toString(): String = "Order(orderId=$orderId, checkId=${check.checkId})"
}

class Check {
    val checkId: String
    val order: Order

    constructor(checkId: String, order: Order) {
        this.checkId = checkId
        this.order = order
    }

    constructor(checkId: String, orderId: String) {
        this.checkId = checkId
        order = Order(orderId = orderId, check = this)
    }

    override fun toString(): String = "Check(checkId=$checkId, orderId=${order.orderId})"
}
but I don't really recommend it. you can't have structural equality or hashcode with self-recursive data
f
yeah looks like even if it could be hacked into existence it would break in runtime lol examples to illustrate problems with circular references. data class Foo(var bar: Bar? = null) data class Bar(var foo: Foo? = null) fun main() { val foo = Foo() val bar = Bar() foo.bar = bar bar.foo = foo println(foo) } This code produces a StackoverflowError. You can try it here. println calls toString() on foo. Since Foo is a data class, its toString() implementation calls the same method on its member properties too. This leads to an ever growing call-stack. https://blog.haroldadmin.com/posts/circular-refs-kotlin
I guess a workaround could be to make the child have the parents string id or something at least so it could look it up in a collection we have access to or something
e
that's why I hand-wrote
toString()
to avoid that, in the example above
depends on your use case but it may be simpler to create a wrapper
data class OrderAndCheck(val order: Order, val check: Check)
to hold them together, without having any direct references from one to the other
f
my brain 🫠 how would that work? we def need the orders to have checks, that's like a fundamental job of theirs
myGod.kotlin.cpp
c
On the JVM, you can't have two immutable objects that reference each other. In your situation, it seems that orders are part of checks, so they should have a 1-way relationship together.
e
On the JVM, you can't have two immutable objects that reference each other.
I gave an example which does exactly that https://kotlinlang.slack.com/archives/C0B8RC352/p1717717766193559?thread_ts=1717717130.233709&cid=C0B8RC352 because objects are not immutable until initialization is completed. leaking
this
before the constructor is complete is not recommended but is easily doable
👀 1
I'd definitely recommend using some external object to hold the bidirectional links instead of having them inside immutable data classes, which I think is where Fred ended up too
f
just as god intended 😆