Kotlin contract with high order functions

298 Views Asked by At

I have problem with Kotlin nullability and I'm wondering am I able to resolve it with contracts. For such Java interface: interface Action<T>{ void execute(T param); } there is two extensions:

fun <T, R> Action<T>.map(mapper:(R)->T): Action<R> {
   return Action{ execute(mapper(it)) }
}

and

fun <T> Action<T>.ifNotNull(): Action<T> {
  return Action { if(it != null) execute(it) }
} 

There is also a generic model with nullable data:

class Model<T>(val data: T?)

Now I have created function which take Action interface as argument. Case is to execute action argument only when param != null, so it looks like below:

fun <T> callback(model: Model<T>, action: Action<T>){
    action
    .map{ it.getData() } //compilation error: getData return T? when action require T
    .ifNotNull() //execute only when data!=null
    .execute(model)
}

So now, is there any option to use Kotlin contract to ensure compiler that action will not execute with null parameter?

3

There are 3 best solutions below

0
On BEST ANSWER

ModelAction in your own answer simply provides the correct signature for ifNotNull():

fun <T> Action<T>.ifNotNull(): Action<T?> {
    return Action { if(it != null) execute(it) }
} 

Then you've got the order of operations wrong:

fun <T> callback(model: Model<T>, action: Action<T>){
    action
    .ifNotNull() // Action<T?>
    .map { model: Model<T> -> model.data } // Action<Model<T>>
    .execute(model)
}

Note that the compiler won't be able to infer R for this map usage. You could also write it as

fun <T> modelAction(action: Action<T>): Action<Model<T>> {
    return action
    .ifNotNull()
    .map { it.data }
}

As a side note, the argument is the "wrong way around" for map; such functions are more commonly called contramap.

0
On

I have created Action interface implementation dedicated for this usage as below:

class ModelAction<T>(val action: Action<T>) : Action<T?> {
    override fun execute(param: T?) {
        param?.let {
            action.execute(it)
        }
    }
}

fun callback<T>(model: Model<T>, action: Action<T>){
    ModelAction(action)
    .map{ it.getData() } 
    .execute(model)
}

This idea may help somebody with similar problem, but it isn't fit my expectations and I still count on solution based on higher-order function.

19
On

All type parameters are shipped as nullable by default if unbounded (in other words, they are derived from Any?). Easy way to fix this is just to specify non-null bound on type parameter: <T : Any>