I'm curious about what is the suggested way to define member functions in Kotlin. Consider these two member functions:
class A {
fun f(x: Int) = 42
val g = fun(x: Int) = 42
}
These appear to accomplish the same thing, but I found subtle differences.
The val
based definition, for instance, seems to be more flexible in some scenarios. That is, I could not work out a straight forward way to compose f
with other functions, but I could with g
. To toy around with these definitions, I used the funKTionale library. I found that this does not compile:
val z = g andThen A::f // f is a member function
But if f
were defined as a val
pointing to the same function, it would compile just fine. To figure out what was going on I asked IntelliJ to explicitly define the type of ::f
and g
for me, and it gives me this:
val fref: KFunction1<Int, Int> = ::f
val gref: (Int) -> Int = g
So one is of type KFunction1<Int, Int>
, the other is of type (Int) -> Int
. It's easy to see that both represent functions of type Int -> Int
.
What is the difference between these two types, and in which cases does it matter? I noticed that for top-level functions, I can compose them fine using either definition, but in order to make the aforementioned composition compile, I had to write it like so:
val z = g andThen A::f.partially1(this)
i.e. I had to partially apply it to this
first.
Since I don't have to go through this hassle when using val
s for functions, is there a reason why I should ever define non-Unit member functions using fun
? Is there a difference in performance or semantics that I am missing?
That's all about the difference between var, val, and def in Scala. In short, the val and var are evaluated when defined, while def is evaluated on call. Also, val defines a constant, a fixed value that cannot be modified once declared and assigned while var defines a variable, which can be modified or reassigned.
main() is a static method and fun() is a non-static method in class Main. Like C++, in Java calling a non-static function inside a static function is not allowed.
In Kotlin functions are declared with the fun keyword and they are first-class citizen. It means that functions can be assigned to the variables, passed as an arguments or returned from another function.
Kotlin is all about Java interoperability and defining a function as a val
will produce a completely different result in terms of the interoperability. The following Kotlin class:
class A {
fun f(x: Int) = 42
val g = fun(x: Int) = 42
}
is effectively equivalent to:
public class A {
private final Function1<Integer, Integer> gref = new Function1<Integer, Integer>() {
@Override
public Integer invoke(final Integer integer) {
return 42;
}
};
public int f(final int value) {
return 42;
}
public Function1<Integer, Integer> getG() {
return gref;
}
}
As you can see, the main differences are:
fun f
is just a usual method, while val g
in fact is a higher-order function that returns another functionval g
involves creation of a new class which isn't good if you are targeting Androidval g
requires unnecessary boxing and unboxingval g
cannot be easily invoked from java: A().g(42)
in Kotlin vs new A().getG().invoke(42)
in JavaUPDATE:
Regarding the A::f
syntax. The compiler will generate an extra Function2<A, Integer, Integer>
class for every A::f
occurrence, so the following code results in two extra classes with 7 methods each:
val first = A::f
val second = A::f
Kotlin compiler isn't smart enough at the moment to optimize such kind of things. You can vote for the issue here https://youtrack.jetbrains.com/issue/KT-9831. In case you are interested, here is how each class looks in the bytecode: https://gist.github.com/nsk-mironov/fc13f2075bfa05d8a3c3
Here's some code showing how f and g are different when it comes to usage:
fun main(args: Array<String>) {
val a = A()
exe(a.g) // OK
//exe(a.f) // does not compile
exe { a.f(it) } // OK
}
fun exe(p: (Int) -> Int) {
println(p(0))
}
Where f
and g
are:
fun f(x: Int) = 42
val g = fun(x: Int) = 42
You can see that g is an object that can be used like a lambda, but f cannot. To use f similarly, you have to wrap it in a lambda.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With