https://kotlinlang.org logo
#arrow
Title
# arrow
j

janvladimirmostert

08/08/2022, 9:18 PM
is there something in Arrow that would allow me to define a
doSomething<T1, Tn, ..., Tn>(...)
that would output
On
In other words, if I provide 5 generics, I want an
O5<T1, T2, T3, T4, T5>
as output and if I define 1 generic, I want
O1<T1>
as output
g

Giorgos Makris

08/08/2022, 9:46 PM
As far as i can see, there is not something like that but this looks a lot like a tuple of n items. What makes your
O
different from a tuple?
j

janvladimirmostert

08/08/2022, 9:56 PM
if my understanding of Tuple is correct, O is a Tuple so that you can do
(a, b, c) = doSomething<Int, String, Boolean>(.....)
99 is just for illustration purposes, probably don't need more than 20 What mechanism would be able to translate function<T1,T2,T3>() to a tuple of 3 items ?
g

Giorgos Makris

08/08/2022, 9:58 PM
this might not be the best solution but i have seen a few java libraries do it but essentially they generate all the code required for up to 20-22 items
j

janvladimirmostert

08/08/2022, 10:00 PM
I'm happy with such a solution too, but fun submit<T1> fun submit<T1, T2> will complain that they are not correctly overloading unless you give them different number of params
it would be even trickier when you have varargs for inputs: fun submit<T1>(varargs inputs: String): O1 fun submit<T1, T2>(varargs inputs: String): O2
g

Giorgos Makris

08/08/2022, 10:02 PM
what if you made those functions values instead and keep them in data classes or a hashmap?
j

janvladimirmostert

08/08/2022, 10:03 PM
something like: val array = Array() array[1] = (vararags) -> O1 array[2] = (vararags) -> O2 ... that actually looks interesting
g

Giorgos Makris

08/08/2022, 10:04 PM
yeah, it could be just like that 🙂
j

janvladimirmostert

08/08/2022, 10:04 PM
although I'll probably need to map it to val array = Array() array[1] = submit1(...) array[2] = submit2(...)
g

Giorgos Makris

08/08/2022, 10:04 PM
not necessarily
j

janvladimirmostert

08/08/2022, 10:05 PM
so how do we go from 5 generics to a number of 5 so that I can do such a lookup ?
g

Giorgos Makris

08/08/2022, 10:06 PM
Copy code
mapof(Pair(O5::class, { o5 -> /* do submit stuff */ }))
you could have sth like that to make the association
and you might have to define an abstract
o
or interface for it to be able to have a common type for the map
j

janvladimirmostert

08/08/2022, 10:08 PM
would this still work for the submit<T1, T2, T3> to somehow output the number 3 so that I can do such a lookup?
unless I make
submit
itself a class, mmm
g

Giorgos Makris

08/08/2022, 10:10 PM
would it work for you if you made submit generic over
O
?
nah the implementation would be different
j

janvladimirmostert

08/08/2022, 10:11 PM
you mean like submit<O> and then depending on whether I have val (a) = submit<O> or val (a, b) = submit<O> it would automatically switch to O1 or O2 ?
g

Giorgos Makris

08/08/2022, 10:12 PM
yeah but it's really hard to imagine one implementation for everything unless it's something that suits you
j

janvladimirmostert

08/08/2022, 10:12 PM
if I can define the output types in the destructuring, that would work too (a: Int, b: String) = submit( ... )
I do need to access a list of those types inside the submit function though
g

Giorgos Makris

08/08/2022, 10:13 PM
i'm not really sure about the destructuring part 😕
j

janvladimirmostert

08/08/2022, 10:14 PM
I could alway just explicitly do submit<O3<Int, String, Boolean>>(...) and be done with it, but I'm trying to avoid having to explicitly define that
something else I've tried
Copy code
val result =  submit(
    i = T(BLAH1("blah1"), BLAH2("blah2")),
    o = T(BLAH1, BLAH1, BLAH2)
)
// want
val (a: BLAH1, b: BLAH1, c: BLAH2) = result
// instead getting
val (a: BLAH1.Companion, b: BLAH1.Companion, c: BLAH2.Companion) = result
but
a
now contains
BLAH1.Companion
instead of
BLAH1
so if I can get rid of the Companion, that'll work too
that example already converts the T to T3 since it has 3 params and works great
g

Giorgos Makris

08/08/2022, 10:17 PM
if you say so 😄 i am a bit lost
j

janvladimirmostert

08/08/2022, 10:18 PM
BLAH1 is defined as class BLAH1 { companion object } if you pass a non-instance of BLAH1 as param to the list, it grabs the companion of Blah1
g

Giorgos Makris

08/08/2022, 10:18 PM
ah, i see
j

janvladimirmostert

08/08/2022, 10:21 PM
if I put bogus values into the o = part,
Copy code
val result =  submit(
    i = T(BLAH1("blah1"), BLAH2("blah2")),
    o = T(BLAH1(null), BLAH1(null), BLAH2(null))
)
I get the correct output types, but that means creating an unnecessary instance for each output type
once again, I can also probably just do
Copy code
val result =  submit(
    i = T(BLAH1("blah1"), BLAH2("blah2")),
    o = T3<BLAH1, BLAH1, BLAH2>()
)
and be done with it, but I'm trying to not specify that 3
g

Giorgos Makris

08/08/2022, 10:25 PM
it's pretty late for me to offer anything better 😛
j

janvladimirmostert

08/08/2022, 10:26 PM
(a, b, c) = submit<O3<T1, T2, T3>>(value1, value2, ...valuen)
is probably not that bad, I'll just have to convince myself that explicitly specifying the number of outputs is awesome, hahaha
thanks for the input, you gave me some things to think about and good night to you! I need to get some Zzz too
g

Giorgos Makris

08/08/2022, 10:27 PM
rest well! if you need anything more feel free to dm me
j

janvladimirmostert

08/08/2022, 10:28 PM
thanks Giorgos!
you too!
the only way I can think of doing this is via code generation, so a compiler plugin that would convert O<T1, T2> to O2<T1, T2> seems like the only way to do this for now I'll just use O2 directly until it becomes easy to write such Compiler plugins
g

Giorgos Makris

08/09/2022, 7:02 AM
generating kotlin code with clojure sounds like a fun project 😛
y

Youssef Shoaib [MOD]

08/09/2022, 7:12 AM
You could just define many overloads
Copy code
fun <T1> doSomething(...): O1<T1>
fun <T1, T2> doSomething(...): O2<T1, T2>
You can easily generate this lol. What I've done in the past is written some Kotlin code that generates it, and placed it at the bottom of that file.
j

janvladimirmostert

08/09/2022, 7:30 AM
you can only do the overloads if they have a different number of params, not if they have varargs
y

Youssef Shoaib [MOD]

08/09/2022, 7:48 AM
Oh right, there is a way around this though. You can do the overloads like this
Copy code
fun <T1> doSomething(vararg args: Type): O1<T1>
fun <T1, T2> doSomething(vararg args: Type, unit1: Unit = Unit): O2<T1, T2>
fun <T1, T2, T3> doSomething(vararg args: Type, unit1: Unit = Unit, unit2: Unit = Unit): O3<T1, T2, T3>
Again, really easy to code generate. Overload resolution then works based on how many type arguments you specify.
p

phldavies

08/09/2022, 8:09 AM
how about using a single typemarker object rather than multiple units - I would guess less impact on the stack that way?
Copy code
sealed interface TypeMarker<out T1, out T2, out T3, out T4, out T5> {
    object IMPL : TypeMarker<Nothing, Nothing, Nothing, Nothing, Nothing>
}

fun <T1> doSomething(vararg args: String, typeMarker: TypeMarker<T1, Nothing, Nothing, Nothing, Nothing> = TypeMarker.IMPL): T1 = TODO()
fun <T1, T2> doSomething(vararg args: String, typeMarker: TypeMarker<T1, T2, Nothing, Nothing, Nothing> = TypeMarker.IMPL): Pair<T1, T2> = TODO()
fun <T1, T2, T3> doSomething(vararg args: String, typeMarker: TypeMarker<T1, T2, T3, Nothing, Nothing> = TypeMarker.IMPL): Triple<T1, T2, T3> = TODO()
fun <T1, T2, T3, T4> doSomething(vararg args: String, typeMarker: TypeMarker<T1, T2, T3, T4, Nothing> = TypeMarker.IMPL): Tuple4<T1, T2, T3, T4> = TODO()
fun <T1, T2, T3, T4, T5> doSomething(vararg args: String, typeMarker: TypeMarker<T1, T2, T3, T4, T5> = TypeMarker.IMPL): Tuple5<T1, T2, T3, T4, T5> = TODO()

fun main() {
    val a = doSomething<String>()
    val (b, c) = doSomething<Int, Long>()
    val (d, e, f) = doSomething<Byte, List<String>, Set<String>>()
}
j

janvladimirmostert

08/09/2022, 8:23 AM
wow! This could actually work All I'mmissing is that I need to be able to access the Companion of T1 inside the function
Copy code
inline fun <reified Z1> doSomething(
   vararg args: String, typeMarker: TypeMarker<Z1, Nothing, Nothing, Nothing, Nothing> = TypeMarker.IMPL
): Z1 {
   Z1::class.Companion ???
   TODO()
}
so the types I'm planning to return looks like this
Copy code
@JvmInline
value class INT4(override val value: ByteArray): Encodable {

   constructor(value: Int) : this(value.toString().toByteArray())
   constructor(value: String) : this(value.toByteArray())

   companion object: Decodable {
      override val code = 23
   }
I need to be able to get the code of that Decodable somehow
Copy code
inline fun <reified Z1: Decodable> doSomething(
   vararg args: Encodable, typeMarker: TypeMarker<Z1, Nothing, Nothing, Nothing, Nothing> = TypeMarker.IMPL
): Z1 {

   Z1::code << ----- I think this is the only missing piece 

   TODO()
}
so that I can do a lookup, if it's code 23, I know I should grab 4 bytes in order to return INT4
this works:
Copy code
fun <T> mapValue(code: Int, value: String): T {
   // .....
}

inline fun <reified Z1> doSomething(
   vararg args: Any, typeMarker: TypeMarker<Z1, Nothing, Nothing, Nothing, Nothing> = TypeMarker.IMPL
): Z1 {
   return when (Z1::class.java.name) {
      INT4::class.java.name -> mapValue(INT4.code, "1234")
      INT8::class.java.name -> mapValue(INT8.code, "5678")
      else -> TODO("add more types")
   }
}
with about 50 types, that
when
is not a great solution
Copy code
val a:INT4 = doSomething<INT4>(INT4(123), INT8(122312))
a
now correctly contains INT4(123) Similarly for 2 outputs
Copy code
inline fun <reified Z1, reified Z2> doSomething(
   vararg args: Any, typeMarker: TypeMarker<Z1, Z2, Nothing, Nothing, Nothing> = TypeMarker.IMPL
): Pair<Z1, Z2> {
   val z1 = when (Z1::class.qualifiedName) {
      INT4::class.qualifiedName -> INT4(INT4.code)
      INT8::class.qualifiedName -> INT8(INT8.code)
      else -> TODO("add more types")
   } as Z1

   val z2 = when (Z2::class.qualifiedName) {
      INT4::class.qualifiedName -> INT4(INT4.code)
      INT8::class.qualifiedName -> INT8(INT8.code)
      else -> TODO("add more types")
   } as Z2

   return Pair(z1,z2)

}
Copy code
val (b, c) = doSomething<INT4, INT8>()
println(b)
println(c)
Outputs: INT4(23) INT8(20) 🎉 now just to optimize that mapping inside doSomething (without using reflection)
p

phldavies

08/09/2022, 9:23 AM
you should be fine to match on the class directly (i.e.
when(Z1::class) { INT4::class -> INT4(INT4.code) }
)
if you don’t mind using full reflection, you can probably also use
val code = Z1::class.companionObjectInstance as? Decodable)?.code ?: error("not decodable type")
g

Giorgos Makris

08/09/2022, 9:27 AM
@phldavies if you don't mind me asking, is the reflection api available in native kotlin?
j

janvladimirmostert

08/09/2022, 9:29 AM
is a
when
with 50 such types the fastest way to do this? I was hoping there's a way to do Z1.code and then use an Array lookup val lookup = Array() lookup[23] = { INT4(it) } lookup[20] = { INT8(it) } I guess I'll have to benchmark the difference in speed between a 100k lookups using reflection to get the code an do the map lookup VS just a giant "switch-case"
p

phldavies

08/09/2022, 9:29 AM
Not as I’m aware - I believe there’s only full reflection in JVM
You’ll find a
when
just as fast as an array lookup by index - possibly faster
j

janvladimirmostert

08/09/2022, 9:32 AM
benchmarks I previously ran showed that the array lookup was faster than the giant switch-case, but I'll redo those to confirm thanks for all the help @phldavies, @Giorgos Makris, @Youssef Shoaib [MOD] , this has been a great learning experience!! btw, that typeMarker solution is awesome!! such a clever way to abuse the varargs
y

Youssef Shoaib [MOD]

08/09/2022, 12:58 PM
@phldavies ironically enough TypeMarker is literally my pattern (at least AFAIR I would call it TypeWrapper with the whole
sealed interface
and a nested
object IMPL
thing) and I literally didn't think of that as a solution lol! I agree that using TypeMarker is probably a good idea here. Btw, you can define multiple arities of TypeMarker as typealiases with a singular
object IMPL
and that'll save you from having to use a lot of `Nothing`s
j

janvladimirmostert

08/09/2022, 1:11 PM
I saw that somewhere in a Kotlin snippet something like this:
Copy code
sealed class TW5<out T1, out T2, out T3, out T4, out T5> {
	@PublishedApi internal object IMPL: TW5<Nothing, Nothing, Nothing, Nothing, Nothing>()
}
typealias TW4<T1, T2, T3, T4> = TW5<T1, T2, T3, T4, Nothing>
typealias TW3<T1, T2, T3> = TW4<T1, T2, T3, Nothing>
typealias TW2<T1, T2> = TW3<T1, T2, Nothing>
typealias TW1<T1> = TW2<T1, Nothing>
y

Youssef Shoaib [MOD]

08/09/2022, 3:27 PM
Yep indeed! It most likely was one of my snippets (probably exploring intersection types). That should help cut down on the amount of arguments you need to define.
j

janvladimirmostert

08/09/2022, 3:39 PM
I still have it bookmarked, it was this one: https://rb.gy/sua1e6 and it was indeed for intersection types
19 Views