Search code examples
genericskotlinkotlin-contracts

Kotlin contract with high order functions


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?


Solution

  • 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.