Kotlin - Generic Conflict in Type Hierarchy - kotlin

I'm trying to build a type hierarchy of query parameters; an object designed to:
Hold a reference to the value to be queried.
Generate an expression of the query to perform over the specified value.
The base class is defined as such:
sealed class QueryParam<V> {
abstract val value: V
}
From the base class, I want to be able to sub-class specific query types; for example...
EqualToQueryParam
class EqualToQueryParam<V>(override val value: V) : QueryParam<V>() {
fun <T, R> toExpression(
property: KProperty1<T, R>,
projection: (V) -> R
): CriteriaExpression<T, Boolean> {
// equal is bound to KProperty1<T, R>
return property.equal(projection(value))
}
}
GreaterThanQueryParam
class GreaterThanQueryParam<V>(override val value: V) : QueryParam<V>() {
fun <T, R : Comparable<R>> toExpression(
property: KProperty1<T, R>,
projection: (V) -> R
): CriteriaExpression<T, Boolean> {
// greaterThan is bound to KProperty1<T, R : Comparable<R>>
return property.greaterThan(projection(value))
}
}
BetweenQueryParam
class BetweenQueryParam<V : Comparable<V>>(override val value: ClosedRange<V>) : QueryParam<ClosedRange<V>>() {
fun <T, R : Comparable<R>> toExpression(
property: KProperty1<T, R>,
projection: (V) -> R
): CriteriaExpression<T, Boolean> {
// Projection in this case doesn't actually make a great deal of sense
// Ideally the projector needs to be removed.
// More than likely in this case V and R are the same thing.
val start = projection(value.start)
val end = projection(value.endInclusive)
// between is bound to KProperty1<T, R : Comparable<R>>
return property.between(start, end)
}
}
The problem is that I have no way of calling toExpression from the base class, essentially rendering this hierarchy useless.
I've tried...
unchecked casts to Comparable<R>
elevating <R> to the class level, rather than the function level, but this isn't useful because then the consumer of the QueryParam needs to know the implementors intention under the hood (it becomes a leaky abstraction).

Related

Kotlin - Infer type for one of two generic parameters

I am trying to create a function that has two generic types: one reified, and another derived from the context of its usage (since it is an extension function):
inline fun <reified E, A> Either<Throwable, A>.bypassLeft(transformation: Throwable.() -> A): Either<Throwable, A> =
when (this) {
is Either.Left -> when (value) {
is E -> value.transformation().right()
else -> this
}
else -> this
}
The idea would be to call the function just mentioning the reified type, something like:
a.bypassLeft<NoResultException> { "" }
In which "a" is an object of type Either<Throwable,String>
But the compiler is not letting me go away with it, and requires me to specify both generic types, instead of deriving the second one form the object calling the function.
It seemed quite a reasonable thing to be possible, but maybe I am wrong...
Is this possible to achieve? If so, what am I doing wrong?
It's not currently possible with a function to ascribe a single type argument and leave the other inferred. You can achieve what you want if you type the lambda arguments by changing your implementation to not use a receiver type.
I threw in there an additional impl that shows how type args can also be partially applied with a class or other surrounding scope.
import arrow.core.Either
import arrow.core.right
inline fun <reified E : Throwable, A> Either<Throwable, A>.bypassLeft(
transformation: (E) -> A //changed to regular arg not receiver
): Either<Throwable, A> =
when (this) {
is Either.Left -> when (val v = value) { //name locally for smart cast
is E -> transformation(v).right()
else -> this
}
else -> this
}
class Catch<A>(val f: () -> A) { //alternative impl with partial type app
inline fun <reified E : Throwable> recover(
recover: (E) -> A
): Either<Throwable, A> =
Either.catch(f).fold(
{
if (it is E) Either.Right(recover(it))
else Either.Left(it)
},
{
Either.Right(it)
}
)
}
suspend fun main() {
val x: Either<Throwable, Int> = Either.Left(StackOverflowError())
val recovered = x.bypassLeft {
s: StackOverflowError -> //here infers E
0 // here infers A
}
println(recovered) // Either.Right(0)
val notRecovered: Either<Throwable, Int> =
Catch {
throw NumberFormatException()
1
}.recover<StackOverflowError> { 0 }
println(notRecovered) // Either.Left(java.lang.NumberFormatException)
}
This is possible as of Kotlin v1.7.0 with the underscore operator.
The underscore operator _ can be used for type arguments. Use it to automatically infer a type of the argument when other types are explicitly specified:
interface Foo<T>
fun <T, F : Foo<T>> bar() {}
fun baz() {
bar<_, Foo<String>>() // T = String is inferred
}
In your example, it would be possible like this:
a.bypassLeft<NoResultException, _> { "" }

Kotlin - TypeReference<T> Cannot obtain Class<*> For Type Arguments

I've created a Kotlin equivalent of TypeReference<T> like so:
abstract class TypeReference<T> : Comparable<T> {
val type: Type get() = getGenericType()
val arguments: List<Type> get() = getTypeArguments()
final override fun compareTo(other: T): Int {
return 0
}
private fun getGenericType(): Type {
val superClass = javaClass.genericSuperclass
check(superClass !is Class<*>) {
"TypeReference constructed without actual type information."
}
return (superClass as ParameterizedType).actualTypeArguments[0]
}
private fun getTypeArguments(): List<Type> {
val type = getGenericType()
return if (type is ParameterizedType) {
type.actualTypeArguments.toList()
} else emptyList()
}
}
In order to obtain Class<*> of the generic type and its arguments, I've also created the following extension function (and this is where I believe the problem lies, since this is where the stack trace fails).
fun Type.toClass(): Class<*> = when (this) {
is ParameterizedType -> rawType.toClass()
is Class<*> -> this
else -> Class.forName(typeName)
}
I'm unit testing this like so:
#Test
fun `TypeReference should correctly identify the List of BigDecimal type`() {
// Arrange
val expected = List::class.java
val expectedParameter1 = BigDecimal::class.java
val typeReference = object : TypeReference<List<BigDecimal>>() {}
// Act
val actual = typeReference.type.toClass()
val actualParameter1 = typeReference.arguments[0].toClass()
// Assert
assertEquals(expected, actual)
assertEquals(expectedParameter1, actualParameter1)
}
The problem I think, lies in the extension function else -> Class.forName(typeName) as it throws:
java.lang.ClassNotFoundException: ? extends java.math.BigDecimal
Is there a better way to obtain the Class<*> of a Type, even when they're generic type parameters?
You need to add is WildcardType -> ... branch to your when-expression to handle types like ? extends java.math.BigDecimal (Kotlin equivalent is out java.math.BigDecimal), ?(Kotlin equivalent is *), ? super Integer(Kotlin equivalent is in java.math.Integer):
fun Type.toClass(): Class<*> = when (this) {
is ParameterizedType -> rawType.toClass()
is Class<*> -> this
is WildcardType -> upperBounds.singleOrNull()?.toClass() ?: Any::class.java
else -> Class.forName(typeName)
}
Note that in this implementation single upper bound types will be resolved as its upper bound, but all other wildcard types (including multiple upper bounds types) will be resolved as Class<Object>
https://github.com/pluses/ktypes
val typeReference = object : TypeReference<List<BigDecimal>>() {}
val superType = typeReference::class.createType().findSuperType(TypeReference::class)!!
println(superType.arguments.first())// List<java.math.BigDecimal>
println(superType.arguments.first().type?.arguments?.first())// java.math.BigDecimal

Why generic sortBy in Kotlin not compiling?

Why it won't compile? It tells there's some error in list.sortBy
fun <T, R : Comparable<R>> Iterable<T>.sortBy(vararg selectors: (T) -> R): List<T> {
return this.sortedWith(compareBy(*selectors))
}
fun main() {
class Person(val name: String, val age: Int)
val list = listOf(Person("Alex", 20))
val sorted = list.sortBy({ it.name }, { it.age })
println(sorted)
}
The error
Type parameter bound for R in
fun <T, R : Comparable<R>> Iterable<T>.sortBy
(
vararg selectors: (T) → R
)
: List<T>
is not satisfied: inferred type Any is not a subtype of Comparable<Any>
When it tries to infer the type R from the first lambda, it's a Comparable<String>. The second lambda returns an Int or Comparable<Int>, which is not a Comparable<String>, so it fails.
You can use star projection for the Comparable type since it doesn't matter if they match.
fun <T> Iterable<T>.sortBy(vararg selectors: (T) -> Comparable<*>): List<T> {
return this.sortedWith(compareBy(*selectors))
}

How to flatMap vavr Either with left variance annotated

My code
open class Fail(override val message: String, override val cause: Throwable?) : RuntimeException(message, cause)
data class ValidationFail(override val message: String, override val cause: Throwable?) : Fail(message, cause)
more fails will be defined there in the future
i have 2 functions
fun fun1(): Either<out Fail, A>
fun fun2(a: A): Either<out Fail, B>
when i try to invoke them like this fun1().flatMap{fun2(it)}
i got
Type mismatch: inferred type is (A!) -> Either<out Fail, B> but ((A!) -> Nothing)! was expected. Projected type Either<out Fail, A> restricts use of public final fun <U : Any!> flatMap(p0: ((R!) -> Either<L!, out U!>!)!): Either<L!, U!>! defined in io.vavr.control.Either
Code from vavr Either:
default <U> Either<L, U> flatMap(Function<? super R, ? extends Either<L, ? extends U>> mapper) {
Objects.requireNonNull(mapper, "mapper is null");
if (isRight()) {
return (Either<L, U>) mapper.apply(get());
} else {
return (Either<L, U>) this;
}
}
I guess o have this error because there is L in flatMap definition not ? extends L
Any workaround for this ?
In your particular case, you can make it compile by removing out variance from fun1 and fun2 return type. You shouldn't use wildcard types as return types anyway.
But it won't help if you have fun1 and fun2 defined this way:
fun fun1(): Either<ConcreteFail1, A>
fun fun2(a: A): Either<ConcreteFail2, B>
Replacing L with ? extends L in flatMap signature will not help either because of ConcreteFail2 not being a subtype of ConcreteFail1. The problem is that Either is supposed to be covariant, but there is no such thing as declaration-site variance in Java. Although there is a workaround using Either#narrow method:
Either.narrow<Fail, A>(fun1()).flatMap { Either.narrow(fun2(it)) }
Of course, it looks odd and must be extracted to a separate extension function:
inline fun <L, R, R2> Either<out L, out R>.narrowedFlatMap(
crossinline mapper: (R) -> Either<out L, out R2>
): Either<L, R2> = narrow.flatMap { mapper(it).narrow }
Where narrow is:
val <L, R> Either<out L, out R>.narrow: Either<L, R> get() = Either.narrow(this)
I think Vavr doesn't provide its own narrowedFlatMap because this method requires using a wildcard receiver type, so it can't be a member method and must be a static one, which breaks all readability of operations pipelining:
narrowedFlatMap(narrowedFlatMap(narrowedFlatMap(fun1()) { fun2(it) }) { fun3(it) }) { fun4(it) }
But since we use Kotlin, we can pipeline static (extension) functions as well:
fun1().narrowedFlatMap { fun2(it) }.narrowedFlatMap { fun3(it) }.narrowedFlatMap { fun4(it) }

How to get around Type mismatch Required: Foo<Type>, Found: Foo<Type?>

Given the following Kotlin code:
class Foo<T>(val t : T?)
fun <T : Any, R : Any> Foo<T?>.transform(transformer : (T) -> R) : Foo<R?> {
return when (t) {
null -> Foo(null)
else -> Foo(transformer(t))
}
}
fun main(args : Array<String>) {
val foo = Foo(args.firstOrNull())
val bar = foo.transform<String, Int> { t -> t.length }
val baz = bar.transform<Int, IntRange> { t -> t..(t + 1) }
}
Why do I get the following error:
Type mismatch. Required: Foo<String?> Found: Foo<String>
If I remove the ? from the extension function to be Foo<T>.transform I instead get the following error:
Type mismatch. Required: Foo<Int> Found: Foo<Int?>
I can understand the second error, because you cannot assign Int? to Int, but the first doesn't make any sense, as you can assign String to String?
EDIT:
I have modified the class Foo<T> to be class Foo<out T> and this works for me as the value t will only ever be read after the initial assignment. With this option I do not need to define the type parameters at the call site of transform.
Another option I have found that I think is a bit messy (and not sure why it makes a difference) is adding a third type parameter to the extension function as follows:
fun <T : Any, U : T?, R : Any> Foo<U>.transform(transformer : (T) -> R) : Foo<R?>
The call site of this on the other hand I find a bit odd. Looking at the above code the call of foo.transform MUST NOT include the type parameters, but the call of bar.transform<Int, Int?, IntRange> MUST include the type parameters in order to work.
This option allows setting the value t at some later point if it were a var instead of val. But it also removes the smart casting on t in the transform function. Although that can be gotten around with a !! if you are not worried about race conditions or (with some additional effort) ?: or ?. if you are worried about race conditions.
You can change your Foo<T> class to be not invariant (see https://kotlinlang.org/docs/reference/generics.html):
class Foo<out T>(val t : T?)
fun <T : Any, R : Any> Foo<T?>.transform(transformer : (T) -> R) : Foo<R?> {
return when (t) {
null -> Foo(null)
else -> Foo(transformer(t))
}
}
fun main(args : Array<String>) {
val foo = Foo(args.firstOrNull())
val bar = foo.transform<String, Int> { t -> t.length }
val baz = bar.transform<Int, IntRange> { t -> t..(t + 1) }
}
The out T specifies precisely the behavior you want.
Since you specify the property t in the constructor as T? you don't need to specify Foo<T?> as receiver and Foo<R?> as return type. Instead use Foo<T> and Foo<R> and it will work.
class Foo<T>(val t : T?)
fun <T: Any, R: Any> Foo<T>.transform(transformer : (T) -> R) : Foo<R> {
return when (t) {
null -> Foo(null)
else -> Foo(transformer(t))
}
}
fun main(args : Array<String>) {
val foo = Foo(args.firstOrNull())
val bar = foo.transform { t -> t.length }
val baz = bar.transform { t -> t..(t + 1) }
}
Note: You don't need to specify the generic types for transform because they can be inferred (at least in this example).