fun <T> doSum(a: T, b: T) : T {
val result : Number = when {
a is Int && b is Int -> a + b
a is Long && b is Long -> a + b
a is Float && b is Float -> a + b
a is Double && b is Double -> a + b
else -> throw IllegalArgumentException()
#Suppress("UNCHECKED_CAST")
return result as T
}
fun <T: Number> doOperation(a: T, b: T, operationToPerform: (T, T) -> T ) {
println(operationToPerform(a, b))
}
I have the method doOperations that takes a generics function as a parameter that I intend to run on the other 2 parameters passed.
However, invoking the same in main as :
fun main(args: Array<String>) {
doOperation (2, 3, doSum)
}
is returning errors like:
Error:(15, 24) Kotlin: Function invocation 'doSum(...)' expected
Error:(15, 24) Kotlin: No value passed for parameter 'a'
Error:(15, 24) Kotlin: No value passed for parameter 'b'
Any suggestions on the way to call doOperation with doSum()?
(& changing < T > doSum to < T: Number > doSum throws up one more error:
Error:(15, 24) Kotlin: Type parameter bound for T in fun doSum(a: T, b: T): T
is not satisfied: inferred type (Int, Int) -> Int is not a subtype of Number)
For anyone that comes in here at a later date, the fix here was to send in the doSum(...) method like this:
doOperation (2, 3, ::doSum)
Adding the :: before doSum(...) allows you to reference a top-level, local, or member function.
More info: https://kotlinlang.org/docs/reference/lambdas.html#function-types
Related
fun main() {
print(test(1, "abc", 4))
}
fun <T> test(criteria: T, actual: T, points: Int): Int {
if (criteria == null) return 0
return if (criteria == actual) points else 0
}
// prints 0
The arguments provided in main function does not match generics contract in the test function since the first argument and the second argument should be of the same type. How does this compile? Tested with Kotlin 1.7.10, here's the kotlin playground link: https://pl.kotl.in/s9fJVUw0D
When you look at the Bytecode of your test function, you will immediately notice the limitation(?) of the JVM when it comes to "generics".
// declaration: int test<T>(T, T, int)
public final static test(Ljava/lang/Object;Ljava/lang/Object;I)I
In other words, Java has this thing called type erasure which is a pain when you come from other generic-supporting languages.
What you maybe want, if you want the code to fail is to play with the Covariance/Invariance rules...
so perhaps:
fun <T: Int?> test2(criteria: T, actual: T, points: Int): Int {
if (criteria == null) return 1
return if (criteria == actual) points else 0
}
Will work for integers.
print(test2(1, 2, 4))
will print 0
print(test2(1, 1, 4))
Will print 4
print(test2(1, "2", 4))
Will fail.
I have the following simple class hierarchy.
abstract class B {
abstract val o : Int
}
class D1 (m: Int, n: Int) : B() {
override val o = m + n
}
class D2 (m: Int, n: Int) : B() {
override val o = m * n
}
I need a "factory function" f that gives me instances of D1 or D2 by calling it as f<D1>() or f<D2>() with hard coded parameters, say 3 and 4. The following doesn't work but illustrates what I need:
// won't compile; just demonstrates what I need
fun < T : B > f () : T {
return T(3, 4) // i. e. return T.constructor(m, n)
}
How to best accomplish this? Any DRY way is fine as long I don't have to repeat 3, 4 all over my code when I instantiate D1 or D2
The only way to do it is via reflection and reified type parameter:
inline fun <reified T : B> f(m: Int = 3, n: Int = 4): T {
val constructor = T::class.constructors.first {
it.parameters.size == 2 &&
it.parameters.all { param -> param.type == Int::class }
}
return constructor.call(m, n)
}
Here's an alternate way without reflection, but you have to manually type out a line for each class you want to handle.
inline fun <reified T : B> f(): T{
val m = 3
val n = 4
return when (T::class) {
D1::class -> D1(m, n)
D2::class -> D2(m, n)
else -> error("Unsupported type ${T::class}")
} as T
}
I'd like to write a generic Kotlin function that works with arbitrary types, as long as they support some basic numeric operations, such as comparison and addition. Something like this (note: this code doesn't compile):
fun <T : ???> twiceTheLarger(a: T, b: T) = if (a > b) a + a else b + b;
In C++, this kind of code works. The following C++ twiceTheLarger function takes anything that supports the + and > operators, be it a primitive numeric type or a custom class:
#include <iostream>
template <typename T> T twiceTheLarger(T a, T b) {
return a > b ? a + a : b + b;
}
int main() {
std::cout << twiceTheLarger(1, 2) << std::endl; // int values
std::cout << twiceTheLarger(42.0, 1.0) << std::endl; // double values
}
How can I get a similar result in Kotlin? The best way I could come up so far is to explicitly pass an object with the required functions, like this:
interface NumericOperators<T> {
fun plus(a: T, b: T): T
fun greaterThan(a: T, b: T): Boolean
}
object IntNumericOperators : NumericOperators<Int> {
override fun plus(a: Int, b: Int) = a + b
override fun greaterThan(a: Int, b: Int) = a > b
}
object DoubleNumericOperators : NumericOperators<Double> {
override fun plus(a: Double, b: Double) = a + b
override fun greaterThan(a: Double, b: Double) = a > b
}
fun <T> twiceTheLarger(a: T, b: T, operators: NumericOperators<T>) =
if (operators.greaterThan(a, b)) operators.plus(a, a) else operators.plus(b, b)
fun main() {
println(twiceTheLarger(1, 2, IntNumericOperators)) // int values
println(twiceTheLarger(42.0, 1.0, DoubleNumericOperators)) // double values
}
Is there a better way?
Edit:
I realize that I could create overloads for each primitive numeric type. The thing is that I'm writing a library function that needs to work with arbitrary number-like types, even types my library doesn't know about. So type-specific overloads are not an option.
You will need to create functions for each primitive type. You can't use <T : Number>, despite all numbers in Kotlin inherit this. Number superclass is used only for the castings.
You'll need to create functions or extension functions:
fun Int.twiceTheLarger(a: Int, b: Int) = if (a > b) a + a else b + b;
fun Double.twiceTheLarger(a: Double, b: Double) = if (a > b) a + a else b + b;
It would be great if you can utilize Comparable<T> in another function for the comparison. Types of T need to overload operator fun plus(other: T) as well.
interface Addable<T>: Comparable<T>{
operator fun <T> Addable<T>.plus(a: T)
}
fun <T : Addable<T>> T.twiceTheLarger(a: T, b: T) {
return if (a > b) a.plus(a) else b + b
}
In Kotlin, I write the following code, which calls the fold function.
fun operation(acc: Int, next: Int): Int {
return acc * next
}
val items = listOf(1, 2, 3, 4, 5)
println(items.fold(1, ::operation))
In the 5th line of the above code, the fold function uses the operation function.
This is reasonable because the fold function is declared to accept a function reference or lambda that takes exactly TWO parameters(3rd line of the following fold implementation from the Kotlin stdlib _Collections.kt)
public inline fun <T, R> Iterable<T>.fold(
initial: R,
operation: (acc: R, T) -> R
): R {
var accumulator = initial
for (element in this) accumulator = operation(accumulator, element)
return accumulator
}
What confuses me is that the fold function can also be fed with a one-parameter function Int::times like below.
val items = listOf(1, 2, 3, 4, 5)
println(items.fold(1, Int::times))
AFAIK, Int::times is declared to be a one-parameter member function as below:
/** Multiplies this value by the other value. */
public operator fun times(other: Int): Int
I don't quite understand the contradiction. Does it have anything to do with the keyword operator?
public operator fun times(other: Int): Int
(or more specifically, ::times) is actually of type Int.(Int) -> Int, which is the same as (Int, Int) -> Int. That allows for both ::times and ::operator to be used with fold.
Currently I am using compose from a library called arrow which has it defined this way.
inline infix fun <IP, R, P1> ((IP) -> R).compose(crossinline f: (P1) -> IP): (P1) -> R = { p1: P1 -> this(f(p1)) }
What I am trying to do is compose functions from a list so I assumed something as simple as this would work.
val add5 = { i: Int -> Option(i + 5) }
val multiplyBy2 = { i: Int -> i * 2 }
fun isOdd(x: Option<Int>) = x.map { y -> y % 2 != 0 }
val composed = listOf(::isOdd, add5, multiplyBy2).reduce { a, b -> a compose b }
but I get type error:
Type inference failed: Cannot infer type parameter IP in inline infix
fun ((IP) -> R).compose(crossinline f: (P1) -> IP): (P1)
-> R None of the following substitutions receiver: (Any) -> Any arguments: ((Nothing) -> Any) receiver: (Nothing) -> Any arguments:
((Nothing) -> Nothing) can be applied to receiver: Function1<, Any>
arguments: (Function1<, Any>)
so I try:
val composed = listOf<(Any) -> Any>(::isOdd, add5, multiplyBy2).reduce { x, y -> x compose y }
and I get this:
Type mismatch: inferred type is KFunction1<#ParameterName Option, Option> but (Any) -> Any was expected
Type mismatch: inferred type is (Int) -> Option but (Any) -> Any was expected
Type mismatch: inferred type is (Int) -> Int but (Any) -> Any was expected
Any help appreciated. I don't mind if I end up having to write my own version of compose. I just need to be able to compose a list of functions.
edit:
This works no problems:
val composed = ::isOdd compose add5 compose multiplyBy2
I am just trying to achieve the same result should I have a list of functions instead of writing this way.
I find it hard to imagine how a simple compose should work with methods having so different signatures. So first we would have to align the types of the functions. Arrow let's you compose functions if the return type of the first matches the parameter of the second...
Another problem is that isOdd is a Predicate. It is not transforming a value.
If the transformers have a compatible signature you can compose them with e.g. andThen
Here is a version that aligns the types to compose the functions. Note that filter and map are special functions in arrow's Option that allow you to pass transformer functions/predicates
import arrow.core.Option
import arrow.core.andThen
import org.hamcrest.Matchers.`is`
import org.junit.Assert.assertThat
import org.junit.Test
class ComposeTest {
#Test
fun shouldCompose() {
val add5 = { i: Int -> i + 5 }
val multiplyBy2 = { i: Int -> i * 2 }
val isOdd = { x: Int -> x % 2 != 0 }
val composed: (Int) -> Option<Int> = { i: Int -> Option.just(i)
.filter(isOdd)
.map(add5.andThen(multiplyBy2))
}
assertThat(composed(3), `is`(Option.just(16)))
assertThat(composed(4), `is`(Option.empty()))
}
}