gmariotti
gmariotti

Reputation: 519

Error using an object for implementing an empty List

I'm trying to rewrite the List interface as an exercise for learning Functional Programming in Kotlin, but I'm not able to figure out why I'm getting an error when I'm trying to have an object as the empty list, that doesn't occur in the Kotlin standard library. In my code, see below, I want to use NIL as a singleton empty list, using list() as the function to return it. However, this generates a type mismatch error on the function of type "Required List. Found NIL"

interface List<A> {
    val empty: Boolean
    val head: A
    val tail: List<A>

    fun cons(a: A): List<A> = Cons(a, this)
}

object NIL : List<Nothing> {
    override val empty: Boolean = true
    override val head: Nothing
        get() = throw IllegalStateException("head called on empty list")

    override val tail: List<Nothing>
        get() = throw IllegalStateException("tail called on empty list")
}

private class Cons<A>(override val head: A,
                      override val tail: List<A>) : List<A> {
    override val empty: Boolean = false
}

fun <A> list(): List<A> = NIL // Type mismatch. Required: List<A>. Found: NIL

fun <A> list(vararg a: A): List<A> {
    var n = list<A>()
    for (e in a.reversed()) {
        n = Cons(e, n)
    }
    return n
}

In the standard library, this error doesn't occur, as you can see in EmptyList in Collections.kt. Am I doing something wrong or am I missing some concepts that make the latter possible and the former not?

Evaluating Proposed Solutions

Of the three solutions proposed, the only one that allows passing the test code below is the one using the anonymous object, while the others throw the exception java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Void.

assertEquals("a", list<String>().cons("a").head)

However, if I change NIL to NIL : List<Any?>, the solution NIL as List<A> works without any problem.

Upvotes: 4

Views: 1227

Answers (2)

holi-java
holi-java

Reputation: 30686

you can cast a List<Nothing> to a List<A> explicit by as keyword, for example:

fun <A> list(): List<A> = NIL as List<A>;

IF you want to suppress the compiler warnings, you can annotated the function with @Suppress annotation, for example:

@Suppress("UNCHECKED_CAST")
fun <A> list(): List<A> = NIL as List<A>;

OR if you don't like this way, you can using an anonymous object in a funtion instead, for example:

fun <A> list(): List<A> = object : List<A> {
    override val empty: Boolean = true
    override val head: A
        get() = throw IllegalStateException("head called on empty list")

    override val tail: List<A>
        get() = throw IllegalStateException("tail called on empty list")
};

Upvotes: 0

gildor
gildor

Reputation: 1894

Generic on your interface should be <out A> instead of <A>.

Also, IntelliJ Idea shows a warning when you use generics without out variance in such cases.

Upvotes: 2

Related Questions