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
- 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.
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()
andequals()
that come for free withdata 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))