Currently, when using sealed types in Kotlin we ge...
# language-proposals
p
Currently, when using sealed types in Kotlin we get auto cast on the subject of the clause. However, if we have something like this:
Copy code
sealed class Input<A> {
  object ForInt : Input<Int>()
  object ForString : Input<String>()
  object ForDouble : Input<Double>()
}

fun <A> process(input: Input<A>): A = when(input) {
  ForInt -> fetchInt() // returns Int
  ForString -> fetchString() // returns String
  ForDouble -> fetchDouble() // returns Double
}
This won’t compile, despite being correct. One way to solve it is like this:
Copy code
@Suppress("UNCHECKED_CAST")
fun <A> process(input: Input<A>): A = when(input) {
    ForInt -> fetchInt() // returns Int
    ForString -> fetchString() // returns String
    ForDouble -> fetchDouble() // returns Double
} as A
The problem with this is that now we get rid of all type safety since we can return any values, which can result on fun runtime errors. We have all the information there, so I think the compiler should be able to infer the type from the input and the case and tell us if we are returning the right type or not :) Is there any plan to support auto cast on the result of a when clause like this? If not, I’ll happily create a KEEP for it.
Furthermore, if we use it with an inline/reified function then we could remove the other branches of the when clause… But that could be a separate feature, maybe 😅
l
I think you want contracts to extend to the returned type, and possibly also have an inference for these contracts.
p
Kind of, I guess is just extending the thing Kotlin does with autocast to the return value of each case and also to that of the final return of the when. Basically having the compiler check that the return of a given case matches the resolved type of the generic (at compile time) and then just do a dirty cast (like in my example) under the hood given that the compiler has verified that all the cases match the expected return type type. (The else case would probably not be allowed in this case as the generic wouldn’t match any hierarchy) Of course, this doesn’t work for logic based
when
clauses and it would be a specialisation of when for sealed classes or interfaces.
l
kotl.in/issue type: feature 😉
🧌 1
p
Yeah, I know, I was asking in case it's too wild or someone already proposed it. Also for some feedback. I've actually received some private feedback that was quite valuable.
l
That feedback can help to make a better feature request, no?
👌 1
h
🤔 the type of the
when
expression seems troublesome. to check its validity, it won't be enough to let it be an anonymous union type (currently it's the least upper bound of the types of the cases -
Any
in the example), but it must be the exact type introduced by the type parameter,
A
. while having the type
A
, the expression must harbour branches of types different from
A
, for a particular
A
. i guess requiring the code to be inlined would make it a bit easier, as the unused branches could be removed, simplifying the type of the
when
expression, but i don't know if that's the Kotlin way (it feels quite C++-ish to me... not that i don't like C++): other Kotlin generics don't work this way.
p
I mean, there is an alternative using extension functions, but requires a bit more work to define it:
Copy code
fun ForInt.process(): Int = fetchInt()
fun ForString.process(): String = fetchString()
fun ForDouble.process(): Double = fetchDouble()
This works, but doesn’t work if we want to have composition of functions. at least not as transparently
r
The issue is related to unification of generic A in a sealed hierarchy that is known in when matching, the last link to the Scala discussion in the comments mentions how it is addressed in scala 2 and 3
🙏 1
This particular issue has affected Arrow all along and you can see in the tests useless casts like
result as A
because Kotlin does not consider unifying the branches of
A
in an exhaustive match.
😣 1