Issue
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.
Solution
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>
Answered By - Jan Vladimir Mostert
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.