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)
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, scala
s 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.