Search code examples
scalasyntaxsemantics

Scala: Syntax causing runtime error?


I am learning Scala and trying to write some command line executables.

I have two version of HelloWorld, which I thought were semantically the same. HelloWorld.scala compiles and runs successfully from the command line. HelloWorld2.scala compiles but produces a runtime error.

My Question: I would think that the two would be semantically the same, so why does the second one produce a runtime error?

Here's the working example:

// HelloWorld.scala

object HelloWorld {

  def main(args: Array[String]): Unit = {
    println("Hello, World!")
  }
}

Here's the broken example:

// HelloWorld2.scala

object HelloWorld2 {

  def main
    : Array[String] => Unit
    = args          => {
      println("Hello, World!")
    }
}

Here's the console output:

java.lang.NoSuchMethodException: HelloWorld2.main([Ljava.lang.String;)
    at java.lang.Class.getMethod(Class.java:1778)
    at scala.reflect.internal.util.ScalaClassLoader$class.run(ScalaClassLoader.scala:66)
    at scala.reflect.internal.util.ScalaClassLoader$URLClassLoader.run(ScalaClassLoader.scala:101)
    at scala.tools.nsc.CommonRunner$class.run(ObjectRunner.scala:22)
    at scala.tools.nsc.ObjectRunner$.run(ObjectRunner.scala:39)
    at scala.tools.nsc.CommonRunner$class.runAndCatch(ObjectRunner.scala:29)
    at scala.tools.nsc.ObjectRunner$.runAndCatch(ObjectRunner.scala:39)
    at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:65)
    at scala.tools.nsc.MainGenericRunner.run$1(MainGenericRunner.scala:87)
    at scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:98)
    at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:103)
    at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)

Solution

  • Regardless that scala can convert a method to a function (with eta-expansion, sometimes automatical), they're different things here. The major difference is that scala generates a different bytecode for JVM.

    Talking about your example, you actually defined a method def that returns an object of class Function1:

    def main: Function1[Array[String], Unit] //you could even put `val` here
    

    when JVM requires a method with completely different signature:

    def main(args: Array[String]): Unit
    

    Because for JVM a function is just an instance of a class FunctionN, scala-compiler doesn't convert it to a method automatically. Manual conversion would look like:

    def main(args: Array[String]): Unit = main.apply(args)
    
    def main: Array[String] => Unit = ...// or `val main`
    

    Note that apply is just a method of Function1 class, () is just a syntax sugar for calling apply.

    Update:

    Just an additional information. As @som-snytt pointed out, scalas runner is more flexible about main's signature, so this:

    def main(args: Array[String]): Int
    

    will work for scala HelloWorld, but won't work for java HelloWorld - it will require Unit (void) as a return type. I can also recall some differences between compiling Java-code with javac vs scalac. So the point is that scala/scalac is evolving, so it might be possible (in future) for scala to run more relaxed main methods, like maybe functional interfaces or something. It can also compile and run scripts, btw.