Title
s

Sam Stone

08/12/2022, 7:29 PM
I have a function that takes two numbers and does simple arithmetic on them. Why do I have to write the same function twice if I want to pass in an
``Int``
or a
``Long``
? Either that, or convert the parameters to the expected type every time. A solution could be including the arithmetic operations in
``Number``
, besides for the conversions.
j

Jacob

08/12/2022, 7:53 PM
you can do it with one function (that will likely have separate cases per type 🙂 )
``fun <T: Number>doArithmetic (a: T, b: T) : Number{``
``}``
s

Sam Stone

08/12/2022, 8:42 PM
... that defeats the point. Why separate it by case when the compiler should do that - I am doing the same operation for each type anyway? That is a lot of unnecessary code duplication.
j

Jan Skrasek

08/13/2022, 12:59 PM
from compiler's pov having Number there means you can combine int and long at the same time. what will be the return type then? this is solved by generics...
r

Roukanken

08/13/2022, 2:23 PM
I am doing the same operation for each type anyway
are you really? Consider simple function, with Int and Long:
``````fun intAddOne(x: Int): Int = x + 1
fun longAddOne(x: Long): Int = x + 1

val a: Int = intAddOne(Int.MAX_VALUE)             // result is lowest Int
val b: Long = longAddOne(Int.MAX_VALUE.toLong())   // result is an smallest long among those that are larger than any Int

a.toLong() == b    // false``````
So, apparently not even
``+ 1``
is same operation for Int & Long.
t

Tom Yuval

08/20/2022, 4:25 PM
It is the same operation, in the same sense that calling an interface function on different implementations of it is the same operation. Imagine there was a
``fun <T : Number> add(x: T, y: T): T``
, then there would be no problem using it in a generic function for
``Number``
subtypes without writing the case for each type separately. Now, this function sort of does exist, only it’s not declared as such, which makes using it impossible, and forces us to write separate functions for separate number types, which is indeed a pity. I suppose the reason is that the normal number types are “primitive” (a notion often claimed to be an implementation detail rather than part of the design of Kotlin, but it seems to have leaked into the language itself in several places), meaning they don’t get compiled as references but as primitive types on the various underlying platforms, and having the basic operations be virtual method calls would force boxing them and would make basic arithmetic so much less efficient. But it does seem plausible that the compiler could optimise that, at the very least for
``inline``
functions with a
``reified``
type parameter. I think it would be a blessed addition to the language!
By the way, the situation is similar with the various
``List``
-like types, including arrays and primitive arrays. And the standard library indeed contains extremely many functions which are duplicated for the various types. The way they go about it there is that the code is written once using a DSL and then from that the various copies are generated automatically (not by the compiler, but in a separate step), but it’s not ideal. It would be very nice to be able to write such functionality once (and in pure Kotlin, rather than a DSL built for that) and have it optimised for the different types by the compiler itself.
s

Sam Stone

08/23/2022, 4:20 AM
@Roukanken firstly, that assumes that my input can be within the range
``Int.MIN_VALUE..MAX_VALUE``
- it is possible that I can guarantee that it won't, in which case your point is moot and the redundancy returns. And even if that was the domain, part of writing the function would be planning for those edge cases, just like any other function. The solution is not necessarily to write another function - much less to write the same exact function with the types and a line or two changed.