Reputation: 7974
I am still learning Kotlin and trying to understand its core principles. What I do not get is this:
fun x() : Int { return 10 }
val y : () -> Int = ::x
val z : () -> Int = { 10 }
fun main(args: Array<String>) {
println(::x)
println(y)
println(z)
}
we get the following output:
fun x(): kotlin.Int
fun x(): kotlin.Int
() -> kotlin.Int
My question is why the output is not the same (I believed these functions should be interchangeable, equivalent)? I think the type of all the functions should be () -> Int
. Why do we keep the original name with the function signature (fun x
) even though it is assigned to a different name (y
)? Is there any language design principle which would require such difference in function signatures?
And a bonus question - why do we need to use the operator ::
. It does not compile without it. But why is this required by the language design? Would not val y = x
work just fine and be much simpler?
Upvotes: 0
Views: 617
Reputation: 809
I think for better understanding look at bytecode
private final static Lkotlin/jvm/functions/Function0; y
@Lorg/jetbrains/annotations/NotNull;() // invisible
// access flags 0x1A
// signature Lkotlin/jvm/functions/Function0<Ljava/lang/Integer;>;
// declaration: kotlin.jvm.functions.Function0<java.lang.Integer>
private final static Lkotlin/jvm/functions/Function0; z
@Lorg/jetbrains/annotations/NotNull;() // invisible
// access flags 0x19
public final static main([Ljava/lang/String;)V
@Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
L0
ALOAD 0
LDC "args"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
L1
LINENUMBER 6 L1
GETSTATIC MainKt$main$1.INSTANCE : LMainKt$main$1;
ASTORE 1
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 1
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
L2
LINENUMBER 7 L2
GETSTATIC MainKt.y : Lkotlin/jvm/functions/Function0;
ASTORE 1
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 1
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
L3
LINENUMBER 8 L3
GETSTATIC MainKt.z : Lkotlin/jvm/functions/Function0;
ASTORE 1
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 1
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
L4
LINENUMBER 9 L4
RETURN
L5
LOCALVARIABLE args [Ljava/lang/String; L0 L5 0
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x19
public final static x()I
L0
LINENUMBER 11 L0
BIPUSH 10
IRETURN
MAXSTACK = 1
MAXLOCALS = 0
// access flags 0x19
// signature ()Lkotlin/jvm/functions/Function0<Ljava/lang/Integer;>;
// declaration: kotlin.jvm.functions.Function0<java.lang.Integer> getY()
public final static getY()Lkotlin/jvm/functions/Function0;
@Lorg/jetbrains/annotations/NotNull;() // invisible
L0
LINENUMBER 12 L0
GETSTATIC MainKt.y : Lkotlin/jvm/functions/Function0;
ARETURN
MAXSTACK = 1
MAXLOCALS = 0
// access flags 0x19
// signature ()Lkotlin/jvm/functions/Function0<Ljava/lang/Integer;>;
// declaration: kotlin.jvm.functions.Function0<java.lang.Integer> getZ()
public final static getZ()Lkotlin/jvm/functions/Function0;
@Lorg/jetbrains/annotations/NotNull;() // invisible
L0
LINENUMBER 13 L0
GETSTATIC MainKt.z : Lkotlin/jvm/functions/Function0;
ARETURN
MAXSTACK = 1
MAXLOCALS = 0
// access flags 0x8
static <clinit>()V
L0
LINENUMBER 12 L0
GETSTATIC MainKt$y$1.INSTANCE : LMainKt$y$1;
CHECKCAST kotlin/jvm/functions/Function0
PUTSTATIC MainKt.y : Lkotlin/jvm/functions/Function0;
L1
LINENUMBER 13 L1
GETSTATIC MainKt$z$1.INSTANCE : LMainKt$z$1;
CHECKCAST kotlin/jvm/functions/Function0
PUTSTATIC MainKt.z : Lkotlin/jvm/functions/Function0;
RETURN
MAXSTACK = 1
MAXLOCALS = 0
}
y and z value have type Function0
static <clinit>()V // here we have static initialization block where y and z are initialized
x have type MainKt$main$1
In each case(println) we just show method declaration(visibility modifiers, return type, signature) without invocation. y and z are high order function that in bytecode are presented by Function0 class, for x value it's just static function.
And when you invoke println(::x) println(y) println(z)
you just print functions declarations. This is ok, that declaration of x and y functions are different that in z function. Because y has reference to x function and z are still high order function that return 10. In simple words y == x, because you assign to y function declaration from x.
Just for Note. In Java I can show method declaration in this way:
public static void main(String[] args) {
try {
System.out.println(Main.class.getMethod("A", null)); // prints public static void Main.A()
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public static void A() {
System.out.println(15);
}
Conclusion: In your code you just print function return type, visibility modifier and signature.
Upvotes: 0
Reputation: 30686
IF you think from my point of view, I think you can understand it immediately.
x
just is an identifier not a variable, so you can't reference it directly.fun x()
is a class which derived from Function0
.::x
is the instance of the type of fun x()
, this is called function reference expression in kotlin.Kotlin makes functions and properties first-class citizens in the language, and introspecting them.
sometimes function has it own class in kotlin like as java-8 lambda expression. but function reference expression can't used with the diff receiver.
when calling an inline function followed with a lambda, both the lambda & the function will be inlined at call-sites. but call with a non-inline function reference expression, only the inline function will be inlined at call-sites.
Upvotes: 0
Reputation: 6373
fun x()
is an ordinary named function from procedural programming. val y
is a property which holds a reference to x
. val z
is an anonymous functional from functional programming.
::
is a 'function reference', a kind of bridge between procedural and functional programming.
By default, your functions should be fun
. Lambda expressions (anonymous functions), on the other side, are intended to be passed to other functions as callbacks.
Upvotes: 1