I'm passing in O(Blah1, Blah1, Blah2)
into a submit function and want to receive O3<Blah1, Blah1, Blah2>
as output, but instead, I'm getting O3<Blah1.Companion, Blah2.Companion, Blah3.Companion>
.
How would I get the compiler to automatically handle O<Blah1.Companion...Blahn.Companion>
input to have an output type of O<Blah1...Blahn>
?
Functions that convers T(1..n)
to Tn
:
fun <T1 : R, R> T(value1: T1) =
T1<T1, R>(value1)
fun <T1 : R, T2 : R, R> T(value1: T1, value2: T2) =
T2<T1, T2, R>(value1, value2)
fun <T1 : R, T2 : R, T3 : R, R> T(value1: T1, value2: T2, value3: T3) =
T3<T1, T2, T3, R>(value1, value2, value3)
T1..Tn
classes:
interface T
class T1<out T1 : R, out R>(
private val value1: T1,
) : T, Iterable<R> {
companion object : T
operator fun component1() = value1
override fun iterator(): Iterator<R> = listOf(value1).iterator()
}
class T2<out T1 : R, out T2 : R, out R>(
private val value1: T1,
private val value2: T2,
) : T, Iterable<R> {
companion object : T
operator fun component1() = value1
operator fun component2() = value2
override fun iterator(): Iterator<R> = listOf(value1, value2).iterator()
}
class T3<out T1 : R, out T2 : R, out T3 : R, out R>(
private val value1: T1,
private val value2: T2,
private val value3: T3,
) : T, Iterable<R> {
companion object : T
operator fun component1() = value1
operator fun component2() = value2
operator fun component3() = value3
override fun iterator(): Iterator<R> = listOf(value1, value2, value3).iterator()
}
Input and Output classes BLAH1..BLAHn:
interface Encodable {
val value: String
}
interface Decodable {
val code: Int
}
class BLAH1(override val value: String) : Encodable {
companion object : Decodable {
override val code: Int = 1
}
}
class BLAH2(override val value: String) : Encodable {
companion object : Decodable {
override val code: Int = 2
}
}
submit function that is receiving O1..n
containing BLAH1.Companion
or Blah2.Companion
that should return O1..n
of types Blah1
and Blah2
without the Companion.
fun <I: Iterable<Encodable>, O: Iterable<Decodable>> submit(i: I, o: O): O {
i.forEach {
// do something with input
println("i: ${it.value}")
}
// ...
// return output
val results = mutableListOf<Decodable>()
o.forEach {
results.add(it)
}
return when (results.size) {
1 -> T1(BLAH1("1:${results[0]}"))
2 -> T2(BLAH1("1:${results[0]}"), BLAH1("2:${results[1]}"))
3 -> T3(BLAH1("1:${results[0]}"), BLAH1("2:${results[1]}"), BLAH2("3:${results[2]}"))
// ...
// 99 -> T99...
else -> TODO("TODO")
} as O // this casting will obviously fail since we're going from BLAH1 to BLAH1.Compaion
}
How do I make submit return O1..n<BLAH1..BLAHn>
when o
contains O1..n<BLAH1.Companion..BLAHn.Companion>
so that the destructuring:
val result = submit(
i = T(BLAH1("blah1"), BLAH2("blah2")),
o = T(BLAH1, BLAH1, BLAH2)
)
val (a, b, c) = result
will actually contain instances of a:BLAH1
, b:BLAH1
, c:BLAH2
instead of a:BLAH1.Companion
, b:BLAH1.Companion
, c:BLAH2.Companion
?
EDIT: Here's what I'm trying to do in the broader sense:
I want to be able to destructure a database query's results directly into values
pool { this: Connection ->
val (a /*:BOOL*/, b /*:INT2*/) = query(
"SELECT a, b FROM table WHERE c = ? AND d = ?",
I(INT4(42), VARCHAR("123"),
O(BOOL, INT2)
)
}
actually, what would be nicer is if I can do:
val (a /*:BOOL*/, b /*:INT2*/) =
query<BOOL, INT2>("SELECT ...", INT4(42), VARCHAR("123")
, but I could not figure out how to convert
query<A>(...) to query(...): O1<A>
query<A,B>(...) to query(...): O2<A,B>
...
query<A,B,...,Z>(...) to query(...): O26<A,B,...,Z>
So I had to put the output
params in the args which meant I had to give up on using varargs for the input
params leading to an api that looks like submit(sql: String, i: I, o: O)
instead.
Note that there can be any number of input
params and I'm expecting at most 26 output
values.
EDIT2: More examples
val (a, b) = query<INT4, INT4>("SELECT 1, 2")
// should output a = INT4(1), b = INT4(2)
val (a, b, c) = query<INT4, VARCHAR, INT4>(
"SELECT col1, col2, col3 FROM table WHERE col4 = ?",
BOOL(true)
)
// should generate a query
// SELECT col1, col2, col3 FROM table WHERE col4 = true
// and output
// a = INT4(value of col1),
// b = VARCHAR(value of col2)
// c = INT4(value of col3)
BOOL, INT4, INT2, VARCHAR are all just value classes each containing a ByteArray with the raw response from the database allowing me to do a.value or c.value which will each return an Int and b.value which will return a String, but this part is kinda irrelevant to the question, INT4 and VARCHAR could just as well have been Int and String.
CodePudding user response:
After digging through source code of other libraries and some help from the Arrow community, I have something that does exactly what I wanted.
I can now call a function with some generics and get those params out as destructured values in a typeSafe way:
(a:A,b:B) = doSomething<A,B>()
(a,b,c,d,...,z) = doSomething<A,B,C,D,...,Z>()
The magic is done with this TypeMarker
sealed class
sealed interface TypeMarker<
out T1 : Encodable,
out T2 : Encodable,
out T3 : Encodable,
out T4 : Encodable,
out T5 : Encodable,
> {
object IMPL : TypeMarker<Nothing, Nothing, Nothing, Nothing, Nothing>
}
and then the TypeMarker
is used at the end of a varargs
fun <reified Z1 : Encodable> doSomething(
vararg args: Any,
typeMarker: TypeMarker<Z1, Nothing, Nothing, Nothing, Nothing> = TypeMarker.IMPL,
): Z1 {
...
}
fun <Z1 : Encodable, Z2 : Encodable> doSomething(
vararg args: Any,
typeMarker: TypeMarker<Z1, Z2, Nothing, Nothing, Nothing> = TypeMarker.IMPL,
): Pair<Z1, Z2> {
...
}
... rinse and repeat for Triple, Tuple4, Tuple5, etc (see the T1-T5 in the original question for an example on how to implement Tuples) and if you want to go beyond 5 params, just increase the number of params in the TypeMarker.
This now allows me to set a generic of INT4 and get one result of INT4 when destructuring or have 99 generics and be able to get 99 params out when destructuring, etc
val a: INT4 = doSomething<INT4>(
INT4(123),
INT8(122312)
)
val (b: INT4, c: INT8) = doSomething<INT4, INT8>(
INT4(123),
INT8(12)
)
val (d,e,f,g,h) = doSomething<INT4, INT8, INT4, INT8, INT4>(
INT4(123),
INT8(12)
)
When dealing with lots of params, you might get annoyed with all the Nothing
s you have to add. You can use this syntax to get around that
sealed class TypeMarker5<out T1, out T2, out T3, out T4, out T5> {
@PublishedApi internal object IMPL: TW5<Nothing, Nothing, Nothing, Nothing, Nothing>()
}
typealias TypeMarker4<T1, T2, T3, T4> = TypeMarker5<T1, T2, T3, T4, Nothing>
typealias TypeMarker3<T1, T2, T3> = TypeMarker4<T1, T2, T3, Nothing>
typealias TypeMarker2<T1, T2> = TypeMarker3<T1, T2, Nothing>
typealias TypeMarker1<T1> = TypeMarker2<T1, Nothing>