I want to define a Generic Algebraic Data Type for use with my parse
function like this:
sealed class Result<T> {
class Success(val value: T, val pos: Int) : Result<T>()
class Failure(val message: String, val pos: Int) : Result<T>()
}
fun <T> parse(t: Parser<T>, input: String, initialPos: Int = 0, collectErrors: Boolean = true) : Result<T> {
However this is not allowed as T
is then an undefined reference.
If I add T
to all member types it works:
sealed class Result<T> {
class Success<T>(val value: T, val pos: Int) : Result<T>()
class Failure<T>(val message: String, val pos: Int) : Result<T>()
}
To me this is somewhat confusing which makes me believe I am missing something here. Why isn't T
seen when defining the member types in the first case?
In addition when creating an instance of Success
I would expect the syntax to be:
Result<T>.Success<T>(tv.someValue, pos)
But that won't work instead I do this:
Result.Success<T>(tv.someValue, pos)
This is the preferable syntax to me but I am struggling to understand why I should leave out T
on Result here.
Result
is a generic class, with a single generic parameter named T. The class name is Result
though, not Result<T>
.
Success
is a generic class, too. So, since it's generic, you need to define it as Success<T>
. If you don't, then it's not generic anymore. Note that, even though it's a subclass of Result which is generic, it could be a non-generic type. For example:
class Success(val value: String, val pos: Int) : Result<String>()
Also note that although Result and Failure are generic, they don't use their generic type for anything. So you could in fact define your classes as
sealed class Result {
class Success<T>(val value: T, val pos: Int) : Result()
class Failure(val message: String, val pos: Int) : Result()
}
Now, why do you need to use Result.Success<T>(tv.someValue, pos)
and not Result<T>.Success<T>(tv.someValue, pos)
?
Because the name of the class is Result.Success
. The parameter type is not part of the class name. Most of the time, it's not necessary to specify it at all because it will be inferred:
val r = Result.Success("foo", 1)
creates an instance of Success<String>
. If you wanted instead to create a Success<CharSequence>
, then you would have to specify the generic type explicitly:
val r = Result.Success<CharSequence>("foo", 1)
or
val r: Result.Success<CharSequence> = Result.Success("foo", 1)
The rules are the same as in Java. It basicly comes down to Success
and Failure
being static nested classes of Result
. There are no "member types" in Kotlin, static nested classes are regular classes that have access to the scope of the outer class. And if a class extends a generic super class, it always needs to bind the generic type parameters.
In contrast, non-static nested classes (denoted by the inner
keyword) always carry the generic type paremeter of the outer class. This way you can construct the following type hierarchy:
open class Foo<T> {
inner class Bar : Foo<T>()
}
To instantiate Bar
you will need to have an instance of Foo
:
val b = Foo<String>().Bar()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With