Why can't the "`main`" function be declared as a lambda in Kotlin?

Viewed 122

The following trivial Kotlin code snippet

fun main() {}

compiles just fine, but the following

val main : () -> Unit = {}

makes the compiler complain that "No main method found in project.", while I was expecting them to be equivalent (I expect a programming language to be as conceptually uniform as possible).

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?

2 Answers

Conceptually, they are different things. To see that, let's take a look at roughly what the equivalent Java would be. I'll use JVM for examples in this answer, but the same principles apply to all of the other Kotlin backends.

object Foo {
  fun main() { ... }
}

This is roughly

class Foo {
  public static void main() { ... }
}

Again, roughly. Technically, you'll get a singleton object and a method on it unless you use @JvmStatic (I assume there's some special handling for main that produces a static function on JVM, but I don't know that for a fact)

On the other hand,

object Foo {
  val main: () -> Unit = { ... }
}

Here, we're declaring a property, which in Java is going to get implemented as a getter-setter pair

class Foo {
  // Singleton instance
  public static Foo instance = new Foo();

  public Supplier<Void> main;

  Foo() {
    main = new Supplier<Void>() {
      Void get() {
        ...
      }
    }
  }

}

That is, there isn't actually a main method. There's a main field which, deep down somewhere, has a function inside of it. In my example above, that function is called get. In Kotlin, it's called invoke.

The way I like to think of it is this. Methods in Kotlin (i.e. the things you define on objects that designate their behavior) are not themselves first-class objects. They're second-class citizens which exist on an object. You can convert them to first-class objects by making them into functions. Functions are ordinary objects, like any other. If you take an ordinary object, which may or may not be a function, and call it with (), then you're actually invoking the method .invoke(...) on it. That is, () is an operator on objects which really ends up calling a method. So in Kotlin, functions are really just objects with a custom invoke and a lot of syntax sugar.

Your val defines a field which is a function. Your fun defines a method. Both of these can be called with (), but only one is a genuine method call; the other is secretly calling .invoke on another object. The fact that they look syntactically the same is irrelevant.

As the old adage goes, functions are a poor man's objects, and objects are a poor man's functions.

There is a subtle (or more than subtle) difference. Declaring it with val means that main is a property containing a reference to an anonymous function (which you defined with the lambda). If you define it with val, then when you call main(), you are actually calling the getter of the main property, and then using the invoke() operator to call invoke() on the return value of the property (the anonymous function).

Related