How to assign generic function to variable?

I would like to assign a generic function to a variable.

Given the following generic function docApply and its exemplary use:

fun <I,O> docApply(f: (I) -> O, n: I): String = "$n -> ${f(n)}"
println(docApply(fun(n: Int): Int = n * n, 3)) // prints 3 -> 9
println(docApply({ n -> n * n }, 2)) // prints 2 -> 4

This is the way I might declare two variables named printer and printer2 parametrized with concrete types for I and O:

val printer = fun(f: (Int) -> Int, n: Int) = docApply(f, n)
val printer2: ((Int) -> Int, Int) -> String = ::docApply

However, my intention is to declare a variable (called printergen) that keeps the generic nature of docApply. Meaning: printer should be a sort of referential “copy” of docApply. In my attempt of doing so Kotlin complains about I and O being unresolved references, of course:

val printergen: ((I) -> O, I) -> String = ::docApply

I don’t find any syntactic means to declare I and O as being type parameters. Any ideas?

BTW: The only solution I could come up with is

fun <I, O> printergen(f: (I) -> O, n: I) = docApply(f, n)

but this adds a level of indirection and is not what I intended to do. I would like to use a val declaration.

1 Like

I don’t think that’s possbile.
At best you can get away dropping the parameters types and just using Any instead of I and O.

Anyway how would you use that variable in a different way than the function itself?

[I don’t get your last paragraph, `printergen` and `docApply` are the same functions.]

Thanks for your reply :smiley:

I guess so since I didn’t find any syntactic means in Kotlin’s grammar to maintain genericy.

I was inspired by https://kotlinlang.org/docs/reference/reflection.html#function-references, see val predicate: ... and just try to understand what is possible in Kotlin and what is not.

They are not the same, printergen calls docApply. Whereas in the Kotlin documentation I referred to, predicate is set with a function instance ::isOdd.

val predicate: (String) -> Boolean = ::isOdd 

My aim is to do the same with printergen and docApply. I have no current use for that, I’m just exploring what’s doable in Kotlin. I’m asking because I might have missed something in the Kotlin specification.

1 Like

There are two points I’d like to contribute. One is to use type aliases:

  typealias PrinterGen<I,O>=((I) -> O, I) -> String

Now your closure object looks like any other object. You can store it in a variable using the same rules as other objects (as a closure is actually just an object).
When you do this you would see that

  val printerGen: PrinterGen<I,O> = ::docApply

can only work if I and O are available in the context. Otherwise you’ll have to use wildcards:

   val printerGen /*:PrinterGen<*,*>*/ = ::docApply

But be aware that the relationships are not gone, so if you pass the variable as a parameter into a function that function can have named generic parameters that are used.

1 Like

Thanks Paul for looking into this! :pray:

Given your thoughts I played around with it a bit. Depite the typealias the code you propose doesn’t work. As soon as a function reference is created via ::docApply, the generic types need to be bound to type values, see e.g. printer2 in my original post at the very top.

Looks like there is no way to get a generic val printergen property as a sort of “copy” from docApply. I like to be proven wrong. Do you have a running example?

I had a quick look with the Kotlin playground. Somehow it wants a concrete type assignment. As you can see in the code below you can do casting afterwards, but it shouldn’t be needed.

fun <I,O> docApply(f: (I) -> O, n: I): String = "$n -> ${f(n)}"

typealias PrinterGen<I,O> = ((I) -> O, I) -> String

val printerGen1: ((Any)-> Any, Any)-> String  = ::docApply
val printerGen2: PrinterGen<Nothing, Nothing>  = ::docApply
val printerGen3: PrinterGen<Any, Any>  = ::docApply
val printerGen4: PrinterGen<Boolean, String>  = ::docApply
val printerGen5: PrinterGen<Int, Int>  = ::docApply

fun main() {
	println(docApply( { n-> n * n } , 2)) // prints 2 -> 4
	println(printerGen1( { n-> (n as Int) * n } , 2)) // prints 2 -> 4
	println(printerGen3( { n-> (n as Int) * n } , 2)) // prints 2 -> 4
	println(printerGen5( { n-> n * n } , 2)) // prints 2 -> 4
}

In this case you seem to have found a limitation in the type system for function types. Look at the following code:

interface PrinterGen {
    operator fun <I,O> invoke(f: (I) -> O, n: I): String
}

typealias PrinterGenAlias<I,O> = Function2<(I) -> O, I, String>

    // This is what the operator invoke looks like here
interface PrinterGenCopy<I, O>: PrinterGenAlias<I,O> {
    override operator fun invoke(f: (I)-> O, n: I): String
}

object docApply : PrinterGen {
    override operator fun <I,O> invoke(f: (I) -> O, n: I): String = "$n -> ${f(n)}"
}

fun PrinterGen(gen: PrinterGenAlias<Any,Any>): PrinterGen {
    // Unfortunately this does create an additional object -- and needs unsafe args
    return object: PrinterGen {
        override operator fun <I2,O2> invoke(f: (I2) -> O2, n: I2): String {
            return (gen as PrinterGenAlias<I2, O2>)(f, n)
        }
    }
}

fun <I,O> docApplyFun(f: (I) -> O, n: I): String = "$n -> ${f(n)}"

val printerGenObj: PrinterGen = docApply
val printerGenFun: PrinterGenAlias<Any, Any> = ::docApplyFun
val printerGenFactory: PrinterGen = PrinterGen(::docApplyFun)

fun main() {
	println(docApply( { n-> n * n } , 2)) // prints 2 -> 4
	println(printerGenObj( { n-> n * n } , 2)) // prints 2 -> 4
	println(printerGenObj( { n-> n + n } , "foo")) // prints foo -> foofoo
	println(printerGenFactory( { n-> n * n } , 2)) // prints 2 -> 4
	println(printerGenFun( { n-> (n as Int) * n } , 2)) // prints 2 -> 4
}```

You can see the difference in the place of the generics of the two interfaces. Unfortunately function types can not be generic themselves. While I'm not 100% on the syntax it should be valid to do this without resorting to the workarounds as my code contains.

Very instructive code examples, Paul!

To summarize: In the first code box you demonstrate that genericy gets in fact lost, ::docApply's type parameters are bound to concrete types. I didn’t had the idea to use type casts to properly call printerGen1/3. Thx. I guess that type erasure is the reason for the need of a type cast.

The second box is another attempt to keep a “copy” of docApply generic (now being an object) by casting it with a generic interface, which the compiler claims to be an unchecked cast and therefore does not compile. A generic type cast doesn’t work.

Given your code that’s the closest I can get with according to my original request, to assign a “copy” to a val property:

interface Printable {
    operator fun <I,O> invoke(f: (I) -> O, n: I): String
}

object docApply: Printable {
    override operator fun <I,O> invoke(f: (I) -> O, n: I): String = "$n -> ${f(n)}"
}

val printerGen = docApply

println(docApply({ n -> n * n }, 9)) // prints 9 -> 81
println(printerGen({ n -> n * n }, 11)) // prints 11 -> 121

Conclusion: References can’t be generic, that’s why type casts can’t be either.