I have a use case for inferring nested generic typ...
# language-proposals
i
I have a use case for inferring nested generic types in a function's definition. Consider the following code:
Copy code
interface Input<T>  { ... }
interface Output<T> { ... }
fun <In: Input<T>, T> registerForOutput(): Output<T> { ... }
Based on the type of
In
, I want to be able to use
registerForOutput
to create an
Output
instance of the same generic type. In this situation, I refer to
T
as the "nested generic type of `In`" in the context of
registerForOutput
. To re-word the initial sentence: I want
registerForOutput
to return an
Output
with the nested generic type of
In
. Consider the following code:
Copy code
class ConcreteInput : Input<String> { ... }
val output = registerForOutput<ConcreteInput, String>()
The code above provides a developer experience that is not ideal. A developer, despite knowing that
ConcreteInput
uses
String
as the generic type for its implementation of
Input
, still needs to explicitly define
String
as a secondary generic type of the call to
registerForOutput
. This is only a minor inconvenience in the case that
Input
has a single generic type, but in the case that
Input
has two, three, or even more generic types, this becomes significantly less enjoyable to work with. I believe that this could be improved by including an "inferred" keyword, which is usable within generic type definitions. Consider the following code:
Copy code
fun <In: Input<inferred T>> registerForOutput(): Output<T> { ... }
In the code above, we state that
T
, the generic type of the generic
In
(the "nested generic" of
In
) should be inferred by the compiler, meaning that it would be possible to write the following code, where the type out
output
should be `Output<String>`:
Copy code
val output = registerForOutput<ConcreteInput>()
The following code should infer that the
T
of
registerForOutput
is
Int
, and the type of
output
should be `Output<Int>`:
Copy code
val output = registerForOutput<Input<Int>>()
The following code should infer that the
T
of
registerForOutput
is
Nothing
, and the type of
output
should be `Output<Nothing>`:
Copy code
val output = registerForOutput<Input<*>>()
Ideally, the
inferred
keyword that I am suggesting should be compatible with
reified
, so that it's possible to write the following:
Copy code
inline fun <In: Input<inferred reified T> registerForOutput(): Output<T> {
    println(T::class.java.simpleName)
    ...
}
I am interested to hear feedback on whether or not this makes sense (or is possible) as a language feature!
2
i
Starting from Kotlin 1.7, you will be able to avoid specifying individual type arguments that could be inferred by replacing them with
_
. So the first call would look like:
Copy code
val output = registerForOutput<ConcreteInput, _>()
Not ideal yet, but avoids stating the inferred type.
i
That's a huge step in the right direction but in the case that a type has multiple generic type arguments, such as
interface Input<A, B, C>
, this is still undesirable, as
registerForOutput
would become
registerForOutput<ConcreteType, _, _, _>
If the compiler is able to understand that
_
is a valid argument, could the compiler completely avoid requiring the
_
type arguments?
d
What if in your example
In : Input<T>
will be the last type parameter of
registerForOutput
, not first? How compiler can understand, that
ConcreteInput
is type argument for last parameter?
i
In my example, with the addition of the
inferred
keyword,
the
registerForOutput
definition would only have a single type parameter:
Copy code
fun <In: Input<inferred A, inferred B, inferred C>> registerForOutput(): Output<A, B, C>
So it would not matter to the compiler - the compiler would attempt to infer the types of
A
,
B,
and
C
, and if it failed to infer the types (for example, if the type was
*
) it would result in a type of
Nothing
.
i
Thanks for that link Ryan!
551 Views