为什么不能在 Kotlin 中将“`main`”函数声明为 lambda?
Why can't the "`main`" function be declared as a lambda in Kotlin?
以下简单的 Kotlin 代码片段
fun main() {}
编译得很好,但下面
val main : () -> Unit = {}
让编译器抱怨“在项目中找不到主要方法。”,而我期望它们是等效的(我希望编程语言在概念上尽可能统一)。
Why does this happen? Is it related only to main
, or does this behaviour concern a larger class of functions? Is there some subtle difference between declaring functions with "fun
" and declaring them as lambdas?
从概念上讲,它们是不同的东西。为了解这一点,让我们粗略地看一下等效的 Java 是什么。我将在这个答案中使用 JVM 作为示例,但相同的原则适用于所有其他 Kotlin 后端。
object Foo {
fun main() { ... }
}
大致是这样
class Foo {
public static void main() { ... }
}
再说一次,大概。从技术上讲,除非您使用 @JvmStatic
,否则您将获得一个单例对象和一个方法(我假设 main
有一些特殊处理在 JVM 上生成静态函数,但我不知道事实上)
另一方面,
object Foo {
val main: () -> Unit = { ... }
}
在这里,我们声明一个 属性,在 Java 中将作为 getter-setter 对
实现
class Foo {
// Singleton instance
public static Foo instance = new Foo();
public Supplier<Void> main;
Foo() {
main = new Supplier<Void>() {
Void get() {
...
}
}
}
}
也就是说,实际上没有 main
方法。有一个 main
字段,在某处深处,其中有一个函数。在我上面的示例中,该函数称为 get
。在 Kotlin 中,它被称为 invoke
.
我喜欢这样想。 Kotlin 中的方法(即您在指定其行为的对象上定义的东西)本身并不是 first-class 对象。他们是存在于 对象上的第二class 公民。您可以通过将它们转换为函数来将它们转换为 first-class 对象。函数是普通对象,就像其他任何对象一样。如果你拿一个普通的对象,它可能是也可能不是一个函数,并用 ()
调用它,那么你实际上是在调用它的方法 .invoke(...)
。也就是说,()
是对象上的 operator,它最终会调用一个方法。所以在 Kotlin 中,函数实际上只是具有自定义 invoke
和大量语法糖的对象。
您的 val
定义了一个字段,它是一个函数。您的 fun
定义了一个方法。这两个都可以用()
调用,但只有一个是真正的方法调用;另一个在另一个对象上秘密调用 .invoke
。它们在语法上看起来相同这一事实无关紧要。
俗话说,函数是穷人的对象,对象是穷人的函数。
存在细微(或不止细微)的差异。用 val
声明它意味着 main
是一个 属性 包含对匿名函数(您使用 lambda 定义)的引用。如果用val
定义,那么当你调用main()
时,实际上是在调用main
属性的getter,然后用invoke()
运算符在 属性(匿名函数)的 return 值上调用 invoke()
。
以下简单的 Kotlin 代码片段
fun main() {}
编译得很好,但下面
val main : () -> Unit = {}
让编译器抱怨“在项目中找不到主要方法。”,而我期望它们是等效的(我希望编程语言在概念上尽可能统一)。
Why does this happen? Is it related only to
main
, or does this behaviour concern a larger class of functions? Is there some subtle difference between declaring functions with "fun
" and declaring them as lambdas?
从概念上讲,它们是不同的东西。为了解这一点,让我们粗略地看一下等效的 Java 是什么。我将在这个答案中使用 JVM 作为示例,但相同的原则适用于所有其他 Kotlin 后端。
object Foo {
fun main() { ... }
}
大致是这样
class Foo {
public static void main() { ... }
}
再说一次,大概。从技术上讲,除非您使用 @JvmStatic
,否则您将获得一个单例对象和一个方法(我假设 main
有一些特殊处理在 JVM 上生成静态函数,但我不知道事实上)
另一方面,
object Foo {
val main: () -> Unit = { ... }
}
在这里,我们声明一个 属性,在 Java 中将作为 getter-setter 对
实现class Foo {
// Singleton instance
public static Foo instance = new Foo();
public Supplier<Void> main;
Foo() {
main = new Supplier<Void>() {
Void get() {
...
}
}
}
}
也就是说,实际上没有 main
方法。有一个 main
字段,在某处深处,其中有一个函数。在我上面的示例中,该函数称为 get
。在 Kotlin 中,它被称为 invoke
.
我喜欢这样想。 Kotlin 中的方法(即您在指定其行为的对象上定义的东西)本身并不是 first-class 对象。他们是存在于 对象上的第二class 公民。您可以通过将它们转换为函数来将它们转换为 first-class 对象。函数是普通对象,就像其他任何对象一样。如果你拿一个普通的对象,它可能是也可能不是一个函数,并用 ()
调用它,那么你实际上是在调用它的方法 .invoke(...)
。也就是说,()
是对象上的 operator,它最终会调用一个方法。所以在 Kotlin 中,函数实际上只是具有自定义 invoke
和大量语法糖的对象。
您的 val
定义了一个字段,它是一个函数。您的 fun
定义了一个方法。这两个都可以用()
调用,但只有一个是真正的方法调用;另一个在另一个对象上秘密调用 .invoke
。它们在语法上看起来相同这一事实无关紧要。
俗话说,函数是穷人的对象,对象是穷人的函数。
存在细微(或不止细微)的差异。用 val
声明它意味着 main
是一个 属性 包含对匿名函数(您使用 lambda 定义)的引用。如果用val
定义,那么当你调用main()
时,实际上是在调用main
属性的getter,然后用invoke()
运算符在 属性(匿名函数)的 return 值上调用 invoke()
。