1
votes

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.

3
You could use lateinit, but that would require manually setting it (and taking it out of the primary constructor). by lazy might also be an option, but AFAIK, it can't be used in the primary constructor either - Zoe
Aha! lateinit seems to be the way to go. See my answer -- I came up with a pretty overhead-heavy way of doing this but lateinit reduces each property back down to 1 line. I'll happily upvote/accept as answer if you post that as an answer. - mejdev

3 Answers

1
votes

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

0
votes

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.

0
votes

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.