https://kotlinlang.org logo
a

Animesh Sahu

09/25/2020, 5:04 PM
Why is there no half implicit generics? Like if there's something like
Copy code
fun <T, E : SomeType<T>> function() {}
Then both T and E needed to be specifed And for example this:
Copy code
fun <T, E : SomeType<T>> function(v: T) {}
if we pass
function(5)
(T) as one of the parameter then E is automatically taken as SomeType<T>, but why cannot we have half implicit parameter like
function<Int>()
that could implicitly say about the second parameter? Or does it exist, I'm just not noticing it?
👆 2
n

Nir

09/25/2020, 5:05 PM
I've needed this before recently as well
a

asad.awadia

09/25/2020, 5:47 PM
what is the use case to have this?
n

Nir

09/25/2020, 6:02 PM
@asad.awadia well, a simple example I guess would be to not lose the actual type, if you wanted to return it
Copy code
fun <T, E : MutableList<T>> foo(e: E, t: T): E {
    e.add(t)
    return e;
}
the way you typically see this written in kotlin is:
Copy code
fun <T> foo(e: MutableList<T>, t: T) ...
but now the actual type of the list is not available
having multiple constraints also makes it pretty easy to create examples I think
a

asad.awadia

09/25/2020, 6:08 PM
I meant more as in what would you be trying to build or develop that requires this?
n

Nir

09/25/2020, 6:23 PM
I guess I don't really understand that question. There isn't much relation between this and building some particular thing you need to build.
It's just a gap in Kotlin's type system/inference that prevents you from doing something convenient/boilerplate saving, in certain cases.
b

bsimmons

09/25/2020, 7:17 PM
@Nir I'm not sure that I understand. Do you have an example of type inference that doesn't work? Both your examples work for me.
n

Nir

09/25/2020, 7:19 PM
@bsimmons you're right 🙂 . I misremembered a bit. The thing that I was trying to do was a bit more involved, and I gave up not because of this piece, but another.
Not because able to put the derived type interfaces, was what got me.
*not being.
Here's what actually got me:
Copy code
fun <T, E : List<T>> foo(e: E, t: T): E {
    return e + t
}
this doesn't compile
I guess it's basically the clone/identity problem.
r

Ruckus

09/25/2020, 10:11 PM
That wouldn't work due to how
plus
it's defined on
List
. You can't guarantee the result is anything other than
List
. No
E
.
n

Nir

09/25/2020, 10:57 PM
@Ruckus yeah I realize. It just tends to be a pain.
a

Animesh Sahu

09/26/2020, 3:26 AM
Sorry I was slept that time. One of the usecase I encountered is I'm trying to make a ObservableCollection pattern using coroutine Flows. Here, a Event looks like this:
Copy code
interface Event<E, C: ObservableCollection<E>> {
    val collection: C
}

// Series of events like ElementAddEvent, ElementReplaceEvent, etc.
class ElementAddEvent<E, C : ObservableCollection<E>>(
    val element: E,
    override val collection: C
) : Event<E, C>
If I wanted to give out this, without loosing the type safety,
Copy code
inline fun <E, C : ObservableCollection<E>, reified T : Event<E, C>> C.on(
    scope: CoroutineScope,
    noinline consumer: suspend (T) -> Unit
): Job =
    events.buffer(Channel.UNLIMITED)
        .filterIsInstance<T>().onEach {
            runCatching { consumer(it) }.onFailure { /*Log exceptions*/ }
        }.catch { /*Log exceptions*/ }
        .launchIn(scope)
Now, to call it, we have to pass all three
E
the element type,
C
the collection type, and
T
the event-type. Well, C and E comes from the collection it was called on, so I guess only T should be a required parameter. Then again, if I try to specify the Event type,
ElementAddEvent<E, C : ObservableCollection<E>>
We have to pass the E and C here inside as well, that too comes from the type of Collection, it seems to redundant to call the function like:
Copy code
observableList.on<Int, ObservableList<Int>, ElementAddEvent<Int, ObservableList<Int>>> {

}
Can we further simplify the generics here? @asad.awadia @bsimmons @Ruckus @Nir
If half implicit generics were there, maybe we can simplify all this to
Copy code
observableList.on<ElementAddEvent> {

}
That could have look much better now!
b

bsimmons

09/26/2020, 5:37 AM
Honestly, I really have no idea what you are trying to do here, but it looks like you are grossly misusing Generic Constraints. Generic constraints should really only be used for limiting the types a generic function can run on, that's it. Don't use it for trying to take a shortcut with some fancy type inference. AFAIK this isn't their purpose. Instead, use generics as placeholders for the types you don't know about and actually type out the types that you do know about in-line. It's alright to keep writing
Event<E>
everywhere. Explicit is better than implicit. Personally, I think doing this makes the code MUCH clearly that the original. (And at worst, you can
typealias
) Here is what I think you want. (Also, I'm not sure what a
ObservableCollection
is, so I just changed it to
Flow
which seems to work)
sample.kt
a

Animesh Sahu

09/26/2020, 7:21 AM
@bsimmons ObservableCollection<> is implementation of the stdlib's MutableCollection<> class (having a property called
event
, that is a flow of Event<>) from where I'm overriding functions like add/remove/set and emitting the different types of event. ObservableList is similar to MutableList, and so on. So I wanted a type-safe event class, like for example if event is emitted from a ObservableList then collection should be ObservableList, and if event is emitted from a ObservableSet then collection variable in that case should be ObservableSet inside Event. Next thing is
.filterIsInstance<Event<E>>()
you've shown does nothing. I want to filter events like ElementAddEvent, ElementReplaceEvent, ElementRemoveEvent.
What's the point of filtering
Event<E>
from Flow<Event<E>>? It will just going to push all the Events at once. Its same as just not filtering any sub-type of event!
b

bsimmons

09/26/2020, 1:37 PM
Ok, I think I understand slightly more. Yes, in this case, we can use a generic constraint to limit a reified generic to only objects of type Event. We can then use this reified generic to avoid erasure and filter on a specific event like
ElementAddEvent<Int>
. I've tried to recreate your
ObservableCollection<E>
as you specified, but it's hard to know if I got that part right. Does this look better?
👍 1
☝️ 1
a

Animesh Sahu

09/26/2020, 1:49 PM
O wow, did not knew star-projections can work like this here!!!
This is perfect! Looks quite neat.
I didn't knew this about star-projections before, what I knew about star projections till now is that it opens as most closest constraint. Like if we specify
<T : CharSequence>
and pass
<*>
and then try to get value, it opens as CharSequence but not actual type like String or StringBuilder for example (I learnt new thing today thanks! 🙂).
b

bsimmons

09/26/2020, 2:15 PM
Yeah, in this case star-projection just means "don't care". We can about
Events
in general, but don't care at all about the generic inside the
Event
. Every day is a learning day. Life, our teacher; and we, her students.
☝️ 1
p

Petter Måhlén

09/28/2020, 7:01 AM
@bsimmons isn’t it a problem that the star projection allows you to write main like this:
Copy code
fun main() {
    val collection: ObservableCollection<Event<Int>> = CustomObservableArrayList(flow {})
    collection.on<ElementAddEvent<String>>(GlobalScope) {
        println("We can successfully type infer this ElementAddEvent<Int> ${it.element + 1}.")
        Unit
    }
}
That, is to choose different values for inner type parameter denoted by the two stars? (String and Int in the example).
a

Animesh Sahu

09/28/2020, 7:26 AM
Ah yea, @Petter Måhlén seems like the method described isn't type-safe. It throws ClassCastExceptions if provided wrong types...
Any more suggestions I could make it type safe in some idiomatic way?
b

bsimmons

09/28/2020, 1:39 PM
Oh true. My bad. I guess I am just completely misunderstanding your situation, sorry. Here is a different made-up situation that is simple enough for my rudimentary frontal lobe to understand. This is personally how I would do this. Hopefully it's helpful and can be adapted for your situation.
(Though the biased object-oriented-is-bad part of me would probably simplify this even further and just filter by an
enum
instead of over-using the type system.)
a

Animesh Sahu

09/28/2020, 1:58 PM
I mean like I knew I could do it using reflection, but it will be worse in performance. This is why the main question arises: why no half implicit generics?
😁
b

bsimmons

09/28/2020, 2:39 PM
I mean, it seemed like your code was very high-level to begin with in terms of performance. Ultimately, you are right, there is a limitation in Kotlin in which you may only introduce one generic at a time and have the extras be implied (i.e. you cannot do something crazy like
<E: Map<F, Event<G>>>
). Maybe someday (but very likely not). I think for your situation you will always have to pass at least two types. The base
E
type, and then the reified type. So
<E, reified T: Event<E>>
, not ideal, but if you truly want access to the reified instance, then both are neccessary.
a

Animesh Sahu

09/28/2020, 2:45 PM
I think T being reified does not mean E to be reified. E is lost at compilation if I'm not wrong. E comes from type of ObservableCollection<E>. I tried some things like:
Copy code
kotlin
observableList.on { event: ElementAddEvent<Int> ->

}
It works, but I really wanted to shift this to Generics as IDE usually lands you on generic at autocompletion. And generic looks (alot) cleaner. If there were optional generics, it could actually look better.
b

bsimmons

09/28/2020, 2:55 PM
Oh true, you can also specify the specific type like that. To me that actually looks the cleanest, you just have to specify the specific type once. The only redundant part is
Int
. I don't think it'll get much cleaner than that.
You cannot fully shift to generics here, because you have to filter on some instance? Why do you think that the example you gave is so dirty? The IDE might not hint very well to it, but that is kinda because we are on the fringes of the type system.