Pre-SIP: program Foo = { println("Hello world!") }

  • What’s the name of the object?

We could name it the name of the file (what Ammonite does) or possibly with a mangling/suffix/prefix to avoid naming conflicts with normally-defined classes. We could do either, since there’s no backwards compatibility concern since *.sc entrypoint files do not currently exist.

  • What about hiding implementation details of the main program block from the rest of the package?

I think we should just ban usage of things in the *.sc entrypoint files from *.scala library files. That would more or less already match the best practices in most scripting languages, and wouldn’t hinder any of the benefits described in either of these threads (getting started with single file, growing to a multi-file project, similarity with other languages, …)

  • What about the static initialization lock?

If we’re going to limit usage of *.sc files in *.scala files as described above, we could wrap the *.sc body in the main method. No more object initialization problems, and the contents of *.sc would be invisible and un-importable from other files as we would want.

1 Like

I’m quite skeptical about approaches of the kind “Let’s add some convoluted magic to make it easier for beginners!”

Transparency is simplicity. If you are going to have stack traces and build tool documentation referring to main methods, then the easiest is to make it obvious what that is.

object HelloWorld {

__ def main(args: String*): Unit = {__

** println(“Hello, World!”)**

** }**

}

I think this is the most elegant approach. It builds on an existing convention (Scala scripts/worksheets), but makes it standard and allows it to be part of full-blown Scala projects seamlessly (each .sc file will generate its own main class).

An .sc file is a “Scala main file”.

Want to see the entry points to a Scala application? Look for the .sc files. Want to run one in particular? Just type sbt run mypackage.foo where foo.sc is the name of the file.

  • versus a very simple, syntactical-only rewriting which takes the existing known correct thing to do , which is an object with a def main , and just introduces syntactic sugar for it, with an obvious user-level meaning.

I just realized that with the “wrap its contents in main methods” way of handling *.sc files, that makes the *.sc approach also a very simple syntactical only rewriting that introduces sugar for the only correct thing to do!

The only difference would be your main body would be in a separate file/extension rather than mixed in with your code in a special program declaration, further reduce hello-world boilerplate, and it would follow the existing script-file convention closely. Other than those three benefits, the two proposals would be identical

@lihaoyi What would you do about deciding what’s the appropriate package for the generated object?

We could allow a package declaration, or leave it in the toplevel package if none is provided

The syntactic rewriting requires a novel language construct (instead of no constructs) and introduces an irregularity into the language, all to save a relatively insignificant amount of boilerplate. It’s just syntactic sugar, but it’s not very much sugar, so it’s not really worth it IMO.

Anyway, I already hinted at some workarounds. Concretely:

  1. The name of the object is foo.Main or foo.ClassFileName.Main
  2. Everything the user writes goes into a main method. foo.Main has hidden vars plus defs corresponding to the vals. The initializer fills these with zeros. The main method fills them in when it “creates” the vals/vars. You use lazy val mechanics rather than object mechanics upon loading the object to run the main method once and init everything.
  3. Write the main method yourself if you want to hide implementation details.
1 Like

Actually, it already does that.

scala Test.scala will not work if it contains a main object, it will run it as a script.

On the other hand, scala Test will look for a compiled class named Test.

I guess the use case we’re addressing is not simple scripting. Here are some use cases which are not covered:

  1. You have a large (compiled) codebase with a lot of entry points. (TBH I’m not sure when this would happen.) The objective is to save the repeated boilerplate.
  2. You want to write a CLI. Writing the boilerplate even once is not ideal.
  3. Scripting. Running scala myscript.scala is inadequate for a few reasons. One is that it doesn’t have access to dependencies, for example from maven central. Ammonite solves this.
  4. Something in between a normal codebase and a script, where normal codebase would mean something that you publish as a polished binary, and script means a single file or a few files. Instead, you want to have a codebase that you can publish as a standalone program, but you also want to easily test things and experiment using scripts. So your codebase (which might be an SBT build) should be accessible from the script.
2 Likes

What about an annotation macro, similar to the ones used by circe and monocle.


@app

class Main(args: Array[String]) {

// ...

}

It would generate something like


class Main(args: Array[String]) {

// ...

}

object Main {

def main(args: Array[String]) = new Main(args)

}

Wouldn’t that solve the issue of the initializer lock?

A separate point… since a major use case is CLIs, we should keep in mind, there are a number of libraries for command-line argument parsing, and any proposal should either work with them well, and/or remove or reduce the need (like Ammonite’s approach).

What happens if I explicitly define both program X and object X? Is the main method inserted in the same way apply and unapply work with case classes?


I tend to agree with @Ichoran that the minor convenience doesn’t pay for the cognitive cost of learning a new construct and new desugaring rule. If there were a way to plug in different runtimes it might be more compelling; i.e., something like

// Default runtime, takes Array[String] => Unit
program MyApp { args => ... }
// Cats runtime, takes List[String] => IO[ExitCode]
// provides implicit ContextShift[IO] and Timer[IO]
program MyApp extends cats.effect.IOApp { args => ... }

But even so there are 95k hits for "program" in Scala projects on GH and at least some of these are identifiers that will become syntax errors if we introduce a new keyword. I’m not convinced it’s worth it.


I agree with @odersky that the = syntax would probably misleading for new users. It makes sense to me to think of program as more like object and less like an assignment.

3 Likes

You just can’t define both an object and a program of the same name, if you need an object you need to write main yourself.

No, this would be a soft keyword, program at the top-level is a parse error right now, so we can give it meaning without breaking existing code.

1 Like

You just can’t define both an object and a program of the same name, if you need an object you need to write main yourself.

If I wrote

program Foo { ... }
object Foo

would it be smart enough to say

Foo is already defined as program Foo

or would it say

Foo is already defined as object Foo

since it would have been desugared to object already? It’s a minor point but it could lead to confusion.

Agreed that good error messages are important, and in that particular case I don’t think it’ll require much effort to make it do the right thing.

I agree with you.
I do not understand why “main function” is so important that we should to add special keyword in the language.

if one add special keywords it should be useful in general
for example:

object Boo extend Program for{
  println("Hello World")
}

It can be used with scalaFx

 new JFXApp.PrimaryStage for{
      
 }

You can make the argument that the main method is a bad idea and it should not be there. But it will always be there. You cannot get rid of it, you can only hide it. Application, App or any of the proposals here do not remove the main method, they only hide it under a layer of sugar.

If you look at the stack trace, or use reflection, you will still see the main method. You can call it, if you like. You may collide with it if you try to define another member of the same name.

If you use a build tool, it might ask you which main method you want to run. SBT has a command “run-main”).

This whole discussion is not about whether there should be a main method. There always will be. The only question is how much sugar you want on top of it.

**Welcome to Scala 2.12.8 (OpenJDK 64-Bit Server VM, Java 1.8.0_191).
Type in expressions for evaluation. Or try :help.

object Hello extends App { println(“Yo!”) }
defined object Hello
Hello.main(Array.empty)
Yo!
object No extends App { def main(args: Array[String]): Unit = println(“Oh, no!”) }
:11: error: overriding method main in trait App of type (args: Array[String])Unit;
method main needs `override’ modifier
object No extends App { def main(args: Array[String]): Unit = println(“Oh, no!”) }
^**

1 Like

Agree. We can even rename App2 to Program, so we get something closer to the proposal of OP:

class Program(entryPoint: => Unit){
  def main(args: Array[String]): Unit = entryPoint
}
object Boo extends Program({
  println("Hello World")
})

If we want args then that’s still easily doable:

class Program(entryPoint: => Unit){
  private var _args: Array[String] = null
  final def args: Array[String] = _args
  final def main(args: Array[String]): Unit = {
    _args = args
    entryPoint
  }
}
object Boo extends Program({
  println(s"Hello World! Args = $args")
})

That’s a flexible solution which doesn’t require any special handling by compiler, doesn’t have any nasty surprises, doesn’t lead to deadlock on loading main class and can be easily extended to support any pre- and post- entryPoint actions. It’s also concise enough it shouldn’t obfuscate small but complete example programs.

Update:
Above example with args doesn’t work. I’ll post fixed version below.

1 Like

It doesn’t work, as args is not in scope in your example. But that’s easily fixed with an implicit function type (as in my example).
I think this shows we really do not need a special language feature for that.

But I’d still like to see the zero-syntax .sc approach, which is already used in practice, be formally made the standard way of defining Scala application entry points.

2 Likes

Thanks for pointing it out. Alternatively args and no args can be handled by secondary constructors:

class Program(entryPoint: Array[String] => Unit){
  def this(entryPoint: => Unit) = this(_ => entryPoint)
  final def main(args: Array[String]): Unit = entryPoint(args)
}

object Foo extends Program(args => {
  println(s"Hello World! Args = $args")
})

object Bar extends Program({
  println(s"Hello World!")
})
1 Like

The class Program whose constructor takes a by-name or an args => body lambda is clever as far as boilerplate reduction goes, but does not help with one of the core concerns: how do we teach a beginner how to write their hello world. It’s even more difficult to explain the block inside the parentheses of the super constructor call than it is to explain the def main(args: Array[String]): Unit = { ... }.

1 Like

Hi, chiming in just to say that if you’re going down the road of special-casing this, also consider the impact it has downstream to anyone who’s not a beginner.

Introducing a program keyword may not be a breaking change for the language (as explained in the proposal), but it’s a breaking change for any tool that needs to parse and understand Scala code (formatters, linters, IDEs, not to mention any other custom tools companies may have).

I tend to agree that this proposal seems a tad too biased towards new developers and the teaching aspects.

Also, while beginners who are just starting programming may find the new syntax appealing, I think this proposal will actively make things more confusing for developers who have experience in another language where having a main method is normal (Java and C, for instance).

To sum it up, considering the (imo negative) impact a new language “shortcut” will have on:

  • existing Scala developers
  • existing developers coming for languages where main is normal and expected
  • existing tools and tooling authors

I don’t think it would be a worthwhile addition to the language.

2 Likes