Adding a new answer after 3 years, as the two existing answers feel incomplete. One describes why it fails without a solution. The other provides a solution without much theory.
fun <P> ifNotNullCreateSpec(vararg propToCreateFun: Pair<P?, (P) -> Specification<T>>)
Given this method signature, what is P
? For one pair it's String
, for another it's Int
. But we can only supply 1 P
as the type parameter of the method.
Can we have P
as Any
? For the first elements in the two pairs, the type checks out, Int
and String
are both subtype of Any?
But for the second element, is (String) -> Specification<T>
a subtype of (Any) -> Specification<T>
? The answer is no. In fact the opposite is true.
(Any) -> ...
is a subtype of (String) -> ...
.
If you replace the parameter's type with a supertype, the function type is a subtype. This is said to be contravariance.
So we cannot have one single P
for the different pairs. I believe the asker already knew this, and was asking how to link up the types inside the pairs - but independent between the pairs.*
Feel free to skip this section which goes full type theory.
This pattern is called existential types**. The idea is that "there exist" a type which links the two inner objects together, but the user of the composed object doesn't care. Funny enough, the example given in Wikipedia is exactly the same as the current question.
"T = ∃X { a: X; f: (X → int); }"
The type T
has a thing of type X
, and a function that takes X
. And when objects of type T
are used, I don't care about what X
is.
To express this pattern in Kotlin, the accepted answer creates a new class WithSpec<P, T>
to link up the P
type between prop
and funCreateSpec
, then uses star projection to ignore the type P
in ifNotNullCreateSpec
.
*That makes the current upvote count quite perplexing.
**Scala used to support existential types with the keyword forSome
, but it was way too fiddly. It was dropped in Scala 3.
P?.ifNonNull(createSpecFun: (P) -> Specification) = this?.let(createSpecFun)`
I take a list of specifications by: `combineSpec(specifications: List>)` and
`combineSpec(listOf("asdf" ifNonNull ::createForName))`
– Alexey Stepanov Apr 15 '19 at 19:04