13

Say I want to declare a simple algebraic datatype for integer lists:

sealed class IntList
data class Cons(val head: Int, val tail: IntList): IntList()
data class Nil() : IntList()

However, the last declaration results in an error

Data class must have at least one primary constructor parameter

  1. Why is this limitation present? Looking at the documentation, there seems to be no good technical reasons for requiring data class constructors to be non-nullary.
  2. Is it possible to express nullary constructors without having to write lots of boilerplate code? If I change the last declaration to something like

    sealed class Nil() : IntList()
    

    then I lose the free implementations of hashCode() and equals() that come for free with data class declarations.

EDIT

Alex Filatov gave a nice short solution below. Obviously, you never need more than one instance of Nil, so we can just define a singleton object

object Nil : IntList()

However, what would we do if our lists were parameterized by a type parameter? That is, now the first two lines of our definition would be

sealed class List<A>
data class Cons<A>(val head: A, val tail: List<A>): List<A>()

We cannot declare a polymorphic singleton Nil object which derives from List<A> for any A, since we have to provide a concrete type for A at the time of declaration. The solution (taken from this post) is to declare A as a covariant type parameter and declare Nil as a subtype of List<Nothing> as follows:

sealed class List<out A>
data class Cons<A>(val head: A, val tail: List<A>): List<A>()
object Nil : List<Nothing>()

This allows us to write

val xs: List<Int> = Cons(1, Cons(2, Nil))
val ys: List<Char> = Cons('a', Cons('b', Nil))
Ulrik Rasmussen
  • 987
  • 1
  • 6
  • 24
  • 1
    Related: https://stackoverflow.com/questions/37873995/how-to-create-empty-constructor-for-data-class-in-kotlin-android But it seems not fit your situation. – BakaWaii Sep 11 '17 at 09:43
  • 1
    Also, "_Note that properties that aren’t declared in the primary constructor don’t take part in the equality checks and hashcode calculation._" – BakaWaii Sep 11 '17 at 09:52
  • @UlrikRasmussen Why do you need `hashCode()` and `equals()` if the class should not contain any field? – Willi Mentzel Sep 11 '17 at 12:35
  • For if I want to compare two lists. But this is only an issue when there can be distinct instances of Nil. By representing Nil as a singleton, the default implementations of these methods will suffice – Ulrik Rasmussen Sep 11 '17 at 12:41
  • @UlrikRasmussen Please post your edit as answer! :) answers should not be in the question – Willi Mentzel Sep 21 '17 at 08:51

3 Answers3

16

Because data class without data doesn't make sense. Use object for singletons:

object Nil : IntList()
Alex Filatov
  • 4,040
  • 2
  • 14
  • 10
0

You will have to create a usual class

class Nil : IntList()

and implement hashCode() and equals() yourself.

A data class without fields does not make any sense, since its job is to represent data.


Or: You can use an object class (as Alex Filatov said) which is a single instance class. Since, you don't need a state for each single Nil instance, they can share a single one.

Willi Mentzel
  • 21,499
  • 16
  • 88
  • 101
-4

If you really want the uniformity in your source code you can use default value to the smallest data type possible.

data class Nil(val _u: Byte = 0) : IntList()

or

data class Nil(val _u: Nothing? = null) : IntList()
Hohenheim
  • 325
  • 1
  • 14