Is there a way to jam a null into a non-nullable type with some sort of "I know what I'm doing"?
I want to make a doubly linked list:
data class Node(var prev: Node, var next: Node, val value: Int)
I can guarantee that this list will have at least two elements at all times except during initialization, after the first node has been added but before the second node. I'd like to make the guarantee that prev and next will never be null.
One thing I thought I'd try is to write a special constructor that initializes both the first and second nodes constructor(v1: Int, v2: Int) : this(Node(this, this, v1), v2)
, but that doesn't work because I can't do anything with this
before entering the body.
I'm wondering if we're doing the same adventofcode puzzle in kotlin
I used lateinit properties to allow the marble to change its position as a node in a linked list
class Marble(val marbleNumber: Long) {
lateinit var counterClockwiseMarble: Marble
lateinit var clockwiseMarble: Marble
}
initially with setters
class Marble(val marbleNumber: Long) {
lateinit var counterClockwiseMarble: Marble
private set
lateinit var clockwiseMarble: Marble
private set
fun setCounterClockwise(m: Marble) {
this.counterClockwiseMarble = m
}
fun setClockwise(m: Marble) {
this.clockwiseMarble = m
}
}
but that seemed like a lot of text when usage was controlled enough to be safe
If it is the same puzzle you can see this used in context on github
I can create properties with null-able backing fields.
data class Node(val value: Int) {
private var _prev: Node? = null
var prev: Node
get() = _prev!!
set(value) {
_prev = value
}
private var _next: Node? = null
var next: Node
get() = _next!!
set(value) {
_next = value
}
constructor(prev: Node, next: Node, value: Int) : this(value) {
this.prev = prev
this.next = next
prev.next = this
next.prev = this
}
}
This can then be initialized via
val node0 = Node(0)
val node1 = Node(node0, node0, 1)
I'm hoping to learn the overhead can be reduced a little bit, but this is better than null-able next/prev.
You could abstract the way a Node
holds its references by providing an interface or a sealed class
e.g.
data class Node(val ref: Reference, val value: Int)
sealed class Reference {
class Forward(val next: Node): Reference()
class Backward(val prev: Node): Reference()
class Bidirection(val prev: Node, val next: Node): Reference()
}
This approach will negate your need for nullable types. It is not very performant, but a declarative way to get compile time type-safety.