Rares Dima
Rares Dima

Reputation: 1769

Kotlin generic number Union?

I want to implement a cumulative sum method for List, such a function should accept List<Int>, List<Float>, etc.

I could go as far as to say that it should accept List<anything that implements add>

But I see no way of specifying this in the official documentation.

I have tried using the type Number but it apparently does not work.

How should I go about making a generic extension function that accepts any type implementing a particular method like add?

Upvotes: 4

Views: 1146

Answers (3)

ice1000
ice1000

Reputation: 6589

In Kotlin, you have these extension functions in the stdlib:

fun Iterable<Byte>.sum(): Int { /* compiled code */ }
fun Iterable<Double>.sum(): Double { /* compiled code */ }
fun Iterable<Float>.sum(): Float { /* compiled code */ }
fun Iterable<Int>.sum(): Int { /* compiled code */ }
fun Iterable<Long>.sum(): Long { /* compiled code */ }
fun Iterable<Short>.sum(): Int { /* compiled code */ }
inline fun <T> Iterable<T>.sumBy(selector: (T) -> Int): Int { /* compiled code */ }
inline fun <T> Iterable<T>.sumByDouble(selector: (T) -> Double): Double { /* compiled code */ }

And from that you can see there isn't a way to write a function for "List of types that have plus method", since Kotlin is not duck typed.

Also, you mentioned List<anything that implements add>, which is unclear (or, clear but incorrect), because in Kotlin, all number types have plus instead of add. From that you can know, different classes have their own definitions of the "add" operation, and such operation have different names under different circumstances.

I recommend you to use the function called reduce, or reduceRight, or fold, or foldRight, which allows you to customize your "add" operation by passing an argument.

Like, the implementation of sum for List<Int> is basically:

fun List<Int>.sum() = fold(0, Int::plus)

And the like.

Upvotes: 2

Alexey Romanov
Alexey Romanov

Reputation: 170899

There is a solution for this in other languages (Haskell and Scala are best-known ones), which is likely to be added in Kotlin eventually: type classes. See https://github.com/Kotlin/KEEP/pull/87 for the (non-final) proposal to add them to Kotlin.

Until they are added, you can do something similar manually:

interface Adder<T> {
    fun add(x: T, y: T): T
}

object IntAdder : Adder<Int> {
    fun add(x: Int, y: Int): Int = x + y
}
// similar for other types

// definition of cumulativeSum
fun <T> cumulativeSum(list: List<T>, adder: Adder<T>): List<T> = ...

// call
cumulativeSum(listOf(1,2,3), IntAdder)

The part type classes solve is that you won't need to pass the adder parameter manually, instead the compiler will determine it based on T.

Upvotes: 1

jrtapsell
jrtapsell

Reputation: 7031

Why you can't add numbers

Numbers only have the following methods:

  • public abstract fun toDouble(): Double
  • public abstract fun toFloat(): Float
  • public abstract fun toLong(): Long
  • public abstract fun toInt(): Int
  • public abstract fun toChar(): Char
  • public abstract fun toShort(): Short
  • public abstract fun toByte(): Byte

There is no add, so you can't add them together

Workarounds

  • You can use the Iterable methods, which should be available on lists
  • You can wrap the Numbers, and have the wrappers supply an add method
  • You could use reflection to do this, but it might not be the best way

Hacky casting workaround

typealias Adder<T> = (T)->T

fun <T: Number> T.toAdder(): Adder<T> {
    return when(this) {
        is Long -> {{it -> (this as Long + it as Long) as T}}
        is Int -> {{it -> (this as Int + it as Int) as T}}
        is Double -> {{it -> (this as Double + it as Double) as T}}
        else -> throw AssertionError()
    }
}

fun <T: Number> List<T>.mySum(zero: T): T {
    return map { it.toAdder() }.fold(zero) { acc, func -> func(acc)  }
}

fun main(args: Array<String>) {
    val total = listOf(1,2,4).mySum(0)
}

This works, but it uses a lot of casting, and should be avoided

Upvotes: 4

Related Questions