Use-case for higher kinded function type

Lately, I stumbled upon a use-case for higher kinded function types. Simplified setting is as follows: A framework offers these interfaces and also instances of those.

interface Box<T> {
    var data: T
}

interface BoolBox : Box<Boolean>
interface IntBox : Box<Int>

The application code:

fun <T> doSomething(box: Box<T>, t: T) {
    box.data = t
}

fun m(box: Box<*>) {
    when (box) {
        is BoolBox -> doSomething(box, true)
        is IntBox -> doSomething(box, 8)
    }
}

Now we also want to execute different implementations of doSomething but we do not want to repeat the when code all the time. So we introduce a helper function.

fun switch(
    box: Box<*>,
    code: ???
) {
    when (box) {
        is BoolBox -> code(box, true)
        is IntBox -> code(box, 8)
    }
}

code should be of some function type but which one fits here? Box is invariant, so (Box<Any>, Any) -> Unit does not work. What we would need here is something like (Box<T>, T) -> Unit where T is a free type variable, making the type expression into a higher kinded type. But this is not directly supported by Kotlin. One has to fall back to non-functional interfaces:

interface BoxCode {
    operator fun <T> invoke(box: Box<T>, t: T)
}

fun switch(
    box: Box<*>,
    code: BoxCode
) {
    when (box) {
        is BoolBox -> code(box, true)
        is IntBox -> code(box, 8)
    }
}

fun usage(box: Box<*>) {
    switch(box, object : BoxCode {
        override fun <T> invoke(box: Box<T>, t: T) {
            doSomething(box, t)
        }
    })
}

For all the usages, we have to use the clunky anonymous object declarations. Do you see more concise alternative solutions for the problem?

2 Likes

The only little improvement I see is making BoxDispatch a fun interface (see Functional (SAM) interfaces | Kotlin). It should look like this (but I didn’t try)

interface BoxDispatch {
    operator fun <T> invoke(): (Box<T>, T) -> Unit
}

fun usage(box: Box<*>) {
    switch(box, BoxDispatch { box: Box<T>, t: T ->  doSomething(box, t)})  
}

If you see the IDE warnings in your code, you see that your examples are underspecified: when requires a complete coverage of all possibilities, but you have only specified the types BoolBox and IntBox.

If we are to assume that these are the only cases, then better specify Box as a sealed interface which means that the derivations specified are the only ones.

If there can be further types, then you may need an else -> clause.

I do not see why the specification of code :(Box<Any>, Any) is not sufficient. Again if you assume that Int and Bool are the only cases, you may need a sealed interface/class together with 2 wrapper classes, but then the example seems to provide little additional use in its generality.

Oh, one thing you could do:

  fun <T> switch(box :Box<T>,
                 code :(Box<T>, T)->Unit
  ) {
    when (box) {
       is BoolBox -> code(box, true)
       is IntBox -> code(box, 8)
       else -> {}
    }
}

The synchronicity between the Box paremeter and the type of the additonal argument, as well as the handed in Box seems necessary. You should check if that compiles, in the worst case, you may need additional casts due to type erasure.

I tried fun interface but Kotlin does not yet support generic functions for SAM interfaces.

(Box<Any>, Any) -> Unit does not work because Box<Boolean> (for example) cannot be assigned to Box<Any>. Box is deliberately invariant, you can read and write T in it.

I tried this but could not modify it in a way such that it compiles and works correctly. Can you be more specific?

I cannot be sure what “works correctly” means exactly, but this compiles for me (just added “as T”):

fun <T> switch(
        box: Box<T>,
        code: (Box<T>, T) -> Unit
) {
    when (box) {
        is BoolBox -> code(box, true as T)
        is IntBox -> code(box, 8 as T)
        else -> {}
    }
}

I used the following usage example for simple runtime check:

fun main() {
    switch(object : IntBox { override var data = 7 }, ::doSomething)
}

fun doSomething(box: Box<Int>, int: Int) {
    println("do something with ${int} on ${box.data}")
}
1 Like

Yes, I tried this, too. But that switch implementation is actually dangerous, reflected by the cast warning. Consider this modified usage. It throws an exception at runtime.

fun <T> switch(
    box: Box<T>,
    code: (Box<T>, T) -> Unit
) {
    when (box) {
        is BoolBox -> code(box, true as T) // unchecked cast!
        is IntBox -> code(box, 8 as T) // unchecked cast!
        else -> {}
    }
}

fun <T> doSth(box: Box<T>, int: T) {
    println("do something with ${int} on ${box.data}")
}

fun main() {
    val box: Box<*> = object : IntBox { override var data = 7 }
    switch(box, ::doSth)
}

I do not see the danger in your example, because doSth expects an IntBox and an Int which are exactly passed in.

I guess the reason for the necessary as T is that the compiler cannot interfere the type T from the is BoolBox condition.

I also found another way to make it work without a common template parameter, but I do consider this dangerous:

  fun switch(box :Box<*>, code :(Box<*>, Any)->Unit) {
    when (box) {
      is IntBox -> code(box, 8)
      is BoolBox -> code(box, true)
      else -> {}
    }
  }

  fun bcode(box :BoolBox, value :Boolean) {
     ...
  }

  data class BBox(val value :Boolean) :BoolBox {}

  fun main() {
     switch(BBox(true), ::bcode as (Box<*>, Any)->Unit)
  }

The last cast is indeed dangerous, because this way you cannot ensure that no e.g. IntBox is passed to the function ::bcode.

Rem: if you forget the method reference (::) for the invocation parameter, bcode is underlined red, but it is not clear how to fix that (there is no tooltip or quick-fix).

Moral: Care about intended types first and use template parameters in method specifications where helpful. Try to use as few typecasts as possible.

The problem with the generic switch function is that the type selection of T comes before the when and therefore the when branches have nothing to do with T. My example exploits this fact, it throws an exception at runtime, try it.

If you had control over the interface, you could just put the method into the interface so that the compiler would get the types right/prevent any misuse:

interface Box<T> {
    var data: T

    fun execute(operation: (Box<T>, T) -> Unit) {
        when (this) {
            is IntBox -> operation(this, 8 as T)
            is BoolBox -> operation(this, true as T)
            else -> {}
        }
    }
}

interface BoolBox : Box<Boolean>
interface IntBox : Box<Int>

fun <T> doSth(box: Box<T>, int: T) {
    println("do something with ${int} on ${box.data}")
}

fun main() {
    val box: Box<*> = object : IntBox { override var data: Int = 7 }
    // compile error: Kotlin: Type mismatch: inferred type is Any? but Nothing was expected
    //box.execute(::doSth)

    // You have to make sure that types match or compiler will reject!
    val intBox = box as IntBox ?: return
    intBox.execute(::doSth)
}

If I understand correctly, you don’t have control over the interface (given by some external library). In that case, you can use a wrapper to emulate that behavior:

class BoxWrapper<T>(private val box: Box<T>) : Box<T> by box {
    fun execute(operation: (Box<T>, T) -> Unit) {
        when (box) {
            is IntBox -> operation(box, 8 as T)
            is BoolBox -> operation(box, true as T)
            else -> {}
        }
    }
}

fun <T> doSth(box: Box<T>, int: T) {
    println("do something with ${int} on ${box.data}")
}

fun main() {
    val box: Box<*> = object : IntBox { override var data: Int = 7 }
    // compile error: Kotlin: Type mismatch: inferred type is Any? but Nothing was expected
    //BoxWrapper(box).execute(::doSth) 

    // You have to make sure that types match or compiler will reject!
    val intBox = box as IntBox ?: return
    BoxWrapper(intBox).execute(::doSth)
}

The compiler still checks for the right types and will reject the dangerous example you’ve given. Also, the call at use-site is simpler than having the “clunky anonymous object declaration” every time - it is only one wrap.

And if creating the wrapper at every usage is still too much, you can additionally use an inline function for it:

inline fun <reified T> Box<T>.switch(
        noinline operation: (Box<T>, T) -> Unit,
        type: Class<T> = T::class.java // probably even works without this parameter
) {
    BoxWrapper<T>(this).execute(operation)
}

fun main() {
    val box: Box<*> = object : IntBox { override var data: Int = 7 }
    // compiler error: Kotlin: Cannot use 'CapturedType(*)' as reified type parameter
    //box.switch(::doSth)

    // You have to make sure that types match or compiler will reject!
    val intBox = box as? IntBox ?: return
    intBox.switch(::doSth)
}

I am not very experienced with inline functions, but this solution seems to provide the wanted compiler type checks (preventing the dangerous example) while giving a perfectly simple usage at call site.

Sorry, this approach does not meet the requirements on the call-site. The goal of this exercise is to abstract away the case analysis on an instance of Box<*>. Doing casts and instanceof checks before using the general switch method (execute in your approach) defeats the purpose of using it.

Thanks for pointing out the problems here. Let me however, comment on these things as well.

It seems a compiler bug that val value :Box<*> = object :IntBox {...}
assumes the object to be of type Box<Void>. The problem seems to arise if a concrete class implements multiple interfaces, especially interfaces with chained inheritance.

On the other hand, why did you mis-specify the type there? If you just skip the misleading type specification, then everything compiles and works. I think this type-specification should be considered an anti-pattern, because it does contribute neither to clarity nor to correct code execution.

Nevertheless, thanks for pointing out the problem.

Now that you mention it, I also suspect it’s a bug. It should not compile, just as tlin47’s last example. But that also means it would not accept Box<*>. However, this is the purpose of the whole story. Otherwise, the switch function is useless.

The star projection (Box<*>) projects a type with the best possible operation availability if the generic parameter type is unknown, i.e. it behaves as Box<out Any> (since T is a subtype of Any) and as Box<in Nothing>. Therefore, on objects of type Box<*>, you can call the methods with T return (e.g. getter), but they will return Any, but you can’t call any methods with T parameter (e.g. setters). (see language spec on star projections)
Simply put, the only way to actually do something interesting on it (like setting a value) is to cast it back to a more meaningful type, e.g. like the when expression does in the examples.

Now, why is Box<*> wanted above by @thumannw? I guess because a variable is needed where you don’t know the actual type parameter - especially since it is probably changing: e.g. first it references an IntBox, but then later it references a BoolBox. But I guess @thumannw can better answer the question about his motivation for it. More details about the actual use-case could also lighten up the problem a bit - I have to admit that I have difficulties to think about an actual use-case to use the examples above (calling an arbitrary-type box with an operation that can handle any generic type, but then creating the actual type T argument for the operation in an obscured “when”-statement… it’s not easy to find a use-case that wouldn’t probably be easier to implement in another way).

2 Likes

External restrictions imposed by framework, as already explained in first post. For clarification, I will introduce another interface. The following interfaces are given by a framework and are not under control:

interface GeneralBox

interface Box<T> : GeneralBox {
    var data: T
}

interface BoolBox : Box<Boolean>
interface IntBox : Box<Int>

I cannot give explanations why the API is like it is, we have to accept it. Next the framework offers an API method returning part of a model, in the form of GeneralBox. Our task is to check which type of box we actually have (holding either Booleans or Ints or …). Then to parse a String for such a value and put it into the box. This implies a when statement as seen above several times. Now, at some other place in our code, we maybe want to just check whether the mysterious box contains the expected value. We now can duplicate the when statement and the parsing of the String, or… (subject of this topic)