ant2009
ant2009

Reputation: 22486

Using multiple generic types in lambda in kotlin

Koltin 1.2.30

I am working with generic and lambda functions.

The following works without the generic types

fun onScanExt(): (Int, Int) -> Int = {
    num1, num2 -> num1 + num2

    num1 + num2
}

However, with the generics:

fun <T, R> onScanExt(): (T, T) -> R = {
    num1, num2 -> num1 + num2

    num1 + num2
}

I guess the above cannot work as the generic type might not be a Number type and someone could pass in a String and the lambda wouldn't know what to do with a String type if there is a calculation involved.

Any suggestions of example on how to pass in multiple generics and return a generic type?

Many thanks in advance,

Upvotes: 9

Views: 2902

Answers (3)

s1m0nw1
s1m0nw1

Reputation: 81869

You're right: The generics, as you used them, allow any type to be used, even the ones that don't offer the + operator for example. Specifying the type to be Number is easy. However, it won't make the function compile either:

fun <T : Number> onScanExt(): (T, T) -> T = { 
    num1, num2 -> num1 + num2
}

(Note that a second type parameter R is not necessary in your example.)

The problem again is that even Number does not include the + operator in its contract. These are defined for the specific types Int, Double et cetera directly (see source).

A strongly typed language such as Kotlin won’t allow such an implementation.

Upvotes: 10

Alexey Romanov
Alexey Romanov

Reputation: 170713

There's no reasonable way to write anything with signature

fun <T, R> onScanExt(): (T, T) -> R

because it says that the caller can choose any T and R, and then you need to obtain an R from two Ts; but there's no relationship between T and R, so you can't use the Ts.

You can make it compile: e.g. use someExpression as R, or throw SomeException(), or an infinite loop. But none of these are actually useful.

In general (C++ is the big exception here; its templates work very differently), a generic method must either:

  1. Work on all types (or, in Java, on all types except primitives). So it can only use operations available on all types, and there are very few of those. An example with two type parameters would be

    fun <T1, T2> apply(f: T1 -> T2, x: T1) = f(x)
    
  2. Work on all types which satisfy some constraints. Different languages allow different kinds of constraints; e.g. C# has a pretty large set, Scala has view and context bounds. Kotlin, so far as I am aware, only allows upper bounds. In particular, there's no constraint which says "type T has operator +". So the function you want can't be written.

Upvotes: 8

Robert Jack Will
Robert Jack Will

Reputation: 11541

For a working example, the lambda body can only call operations on the generic values which their supertype guarantees to be present (as you already said). So some very generic working examples could be:

fun <T> onScanExt(): (T, T) -> String  = { a, b ->
    a.toString() + b.toString()
}

Or:

fun <T: Comparable<T>> onScanExt(): (T, T) -> T  = { a, b ->
    if (a.compareTo(b) < 0) a else b
}

Fun fact: while generic lambdas can be returned from functions, they cannot be (generically) stored in fields or variables:

val f = onScanExt2()

This fails to compile with the error:

Not enough information to infer parameter T [..]. Please specify it explicitly.

Very much unlike purely functional languages like good-old Haskell where there is really no such difference between functions and variables containing functions. If you like it simple, then maybe try Haskell!

Upvotes: 4

Related Questions