Proposal: Main methods (`@main`)

Scala Native and Scala.js deal with main methods in exactly the same way as Scala/JVM, and will have to support whatever system gets added to the compiler (for starters, because it will be in the typechecker and call something from the stdlib, which cannot be overridden in Native/JS). I’ll argue that one cannot claim that the mechanism is but a host system interop thing.

3 Likes

If you think it needs to be in the spec, we should just concentrate on a small core that is guaranteed to be supported without preventing any of the proposed refinements.E.g. something like this:

A @main annotation on a method defines an entry point to start a program. A Scala implementation has to support @main at least on methods that have one the following signatures (where the names of the method and its parameter are arbitrary):

@main def f(): Unit
@main def f(args: String*): Unit

But it is free to support other formats as well.

1 Like

The thing is that, however you phrase the text of the spec, the fact is: anything that Scala/JVM 3.0 supports (without flag) will act as de facto specification, and will have to be supported forever and by all platforms.

“Platform-specific” has no meaning in the spec. There were “platform-specific” aspects already in the Scala 2.9 specification, and it didn’t matter. The behavior of Scala/JVM is de facto spec, because people end up relying on it. That’s why Scala.js had to go to so much trouble to reimplement very faithfully a number of things that are supposedly platform-specific in the spec.

If the codegen generates a static def main(args: Array[String]): Unit, method, that’s all the JVM, Native and JS care about. “Host integration” for main methods has already been specified de facto years ago as being a call to that specific static method. Everything else is the language/frontend’s business, and will be supported by all platforms. And once it is in a release without a flag, it will be used, and will have to be supported forever.

My conclusion is: what’s not spec’ed should be behind a flag. Conversely, if something is not behind a flag, it must be spec’ed (because, de facto, it will be spec’ed by the implementation if not by the text).

2 Likes

By that logic we’d also have to spec the standard library, correct? What about macro annotations? The things we talk about are already supported in libraries, and some of them are used pervasively.

The standard library is indeed spec’ed, by its code in the worst case and by its Scaladoc in the best case. They rely on the underlying spec of the language. And yes, once things are in the stdlib, it becomes extremely difficult to change them, causing significant migration pain. But at least for the stdlib, if we want to completely remove something, we can add it back in a separate library. For language features, we cannot do that.

Macro annotations would also be specified by their code and documentation. In the same way. If they are in the stdlib, they are subject to the same compatibility requirements (including the ability to export it in separate libraries if required). The mechanism that implements macro annotations is part of the language spec, and therefore is subject to the same requirements as the language: once it’s in, it’s virtually impossible to get rid of.

But if you don’t like it or there is a new way of doing things better, you can drop using the old library and start using a new one with better principles. You cannot do that with language features; you have to keep them all in the compiler forever.

2 Likes

As the discussion is not really settled to one approach, I personally would appreciate a -X flag so that we can still break things a next minor version

1 Like

I think we have to acknowledge there’s a grey area. For instance, Scala 3 supports

@threadunsafe lazy val x 

That’s not in the spec. The SIP process will probably not be concerned with it. Yes, it means practically speaking we have to support it forever, but it’s probably not the job of the SIP committee to
concern itself with this.

Or take async/await. Not specced, yet implemented in the Scala 2 compiler.

Or take any Java annotation. They come in libraries, some of them in the standard Java library. Some of them change semantics through bytecode rewriting or whatever. Yet they are not considered part of the language.

So, I think we can agree that certain things cannot be changed at will but that does not mean they should be part of the language spec. Otherwise, it would mean that the SIP committee has a duty to define precisely, and a right to veto, everything that can be expected to be in a Scala distribution. And that clearly surpasses it’s charter and capabilities.

1 Like

It isn’t. It’s in a macro in a separate library. There’s a PR to include it in the compiler, but only under a flag.

The language spec mandates that they are transferred to the bytecode, so that the underlying run-time can apply their semantics. None of them change semantics by themselves. They need separate bytecode rewriters distributed outside of the language. The only ones that actually affect run-time semantics, like @transient, are in fact specified in the language.

The SIP committee definitely does not concern itself with the code and spec of the libraries, including the standard library and including macros. This has always been considered out of scope (and yet even that was deemed too lax once, regarding the change to Predef.Seq to mean immutable.Seq instead of collection.Seq).

However, everything that the compiler does and that affects language semantics (so, not optimizations) has so far been considered to be the SIP committee’s duty. Otherwise, the changes should be considered “compiler features” rather than “language features”.

Is @threadUnsafe a compiler feature? Perhaps. At least that one is probably never going to be used, or only by very savvy users (and, worst case, its support can be dropped, since the semantics of thread-safe lazy vals are strictly more specified than thread-unsafe ones).

Is @main a compiler feature? I would hope not, because it will be on Page 1 of every book.

2 Likes

@main probably does not need to be a compiler feature once we have annotation macros that can do codegen. So I believe we can wait for that to materialize first.

3 Likes

This thread has now been open for 26 days. If anyone wants to add anything further or make any kind of closing or summary statement for the committee, please do so soon, before the thread closes (after 30 days).

IIUC: The final version is:

object add:
  private val $main = new main()
  private val $main$wrapper = $main.wrapper(
    "MyProgram.add",
    """Adds two numbers
      |@param  num   the first number
      |@param  inc   the second number""".stripMargin)
  def main(args: Array[String]): Unit =
    val cll = $main$wrapper.call(args)
    val arg1 = cll.nextArgGetter[Int]("num", summon[$main.ArgumentParser[Int]])
    val arg2 = cll.nextArgGetter[Int]("inc", summon[$main.ArgumentParser[Int]], Some(1))
    cll.run(myProgram.add(arg1(), arg2()))
end add

if it is so it will completely meet my requirements.
It has readable names and enough power.