There has been a lot of discussion recently about ways we can simplify people’s getting started experience with Scala, particularly around the definition of main methods or program entrypoints:

I think it’s worth going over why we would want to do this, from first principles. Rather than starting with any specific feature or syntax we want to add, I will review the current state of the world, why exactly an end user would care about us simplifying the Scala getting started experience, and one possible path to get there.

Why not to Simplify Scala’s getting started experience

There are several reasons for simplifying the Scala definition of main methods or program entry points. I think many of them are invalid, and will go through the proposals here.

Currently, defining a Scala program involves the following:

object Bar { def main(args: Array[String]): Unit = { println("hello world") } }

This is a bunch of boilerplate, with two existing simplifications that have their own issues:

object Bar extends Application { println("hello world") }

This runs the initialization code in the Object’s static initiializer, which has a bunch of issues. An alternative,

object Bar extends App { println("hello world") }

relies on the magic DelayedInit trait, which is deprecated and going away.

Various proposals have slimmed it down to:

object Bar { def main: Unit = { println("hello world") } }

program Bar = { println("hello world") }

These changes have a cost: we are moving away from the standard JVM style of declaring main methods. But in exchange for that cost, these simplifications buy us very little over an existing user-land helper:

class App2(x: => Unit){ def main(args: Array[String]): Unit = x } object Bar extends App2({ println("Hello World") })

This is pretty concise already, and both the def main: Unit proposal and program proposal barely reduce boilerplate to below what is already possible with a helper App2 class as shown above. Thus I do not think they are worth doing.

Why to Simplify Scala’s getting started experience

I propose that the main reason to simplify Scala main method definitions is to smooth out the transition between Scala programs of different size. This section will be written entirely from the user’s point of view: exactly how we should implement internally, whether in the build tool or compiler or somewhere else, is very much not the end user’s problem.

Currently, the following sizes Scala programs exist:

1-liners in the scala REPL

REPL 1-10 liners in the amm REPL

REPL 10-1000 liners in scala or amm scripts

or scripts 1000+ liners in projects using sbt , mill , or other build tools.

Currently this is not ideal, for the following reasons:

Needing to swap from the scala REPL to the amm repl for 1-10 line programs is arbitrary and a waste of time

scala scripts are unmaintained, undocumented, and generally not production ready.

Converting from an 10-1000 line amm script to a 1001 line sbt or mill project involves creating a bunch of ancillary files, putting things in subfolders (SBT now suggests a multi-subproject setup by default, and Mill only supports subprojects), and wrapping things in main boilerplate as described above

In an ideal world, someone getting started with Scala would be able to use the same set of tools to grow their program from 1 line, to 10 lines, 100 lines, 1000 lines, and beyond without needing to swap between 3 arbitrarily different code runners (e.g. scala , amm , mill ). A newbie should be able to:

Start out in the REPL:

> println("hello world")

Grow the program to multiple lines:

> println("hello world") println("i am cow")

Save the program to a file to run

// Hello.sc println("hello world") println("i am cow")

Introduce multiple files as the program grows:

// Hello.sc import $file.Cow println("hello world")

// Cow.sc println("i am cow")

Introduce a proper build tool once that becomes useful:

// Hello.sc hello() cow()

// Hello.scala def hello() = println("hello world")

// Cow.sc def cow() = println("i am cow")

// build.sc or build.sbt ...

And as the project grows, breaking up the single-module-build into individual submodules or subprojects

Each of these workflows should be official, supported, provide a good user experience, and a smooth transition to the next level. That is very much not the case with the current setup of official/poor-UX Scala REPL, official/unsupported Scala Scripts, unofficial/good-UX Ammonite REPL/Scripts, and the messy transition from scripts to a proper SBT/Mill/etc. project.

This should make it much easier for people getting started with Scala:

Software engineers who are just learning Scala can easily start off with a clean, simple environment (REPL, scripts) without needing to worry about SBT configuration and other irrelevant things

When the time comes to upgrade their project, they can do so without needing to re-write their code, re-name all their files, and add irrelevant def main boilerplate.

Professional novices, who may use Scala incidentally but are not professional programmers, can rely on Scala to provide a good user experience for their small programs in the REPL or scripts without ever wanting, or needing, to learn the intricacies of doing things “properly” with a complex build tool like SBT.

Simplifying and stabilizing support for Scala programs of size 1-1000 lines would not just help professional software engineers getting started with Scala. There is a very large class of data scientists, analysts, system admins, devops, mathematicians, mechanical engineers, and others who would fall under this category: these are people who may spend the majority of their career in the 1-1000 line program phase, only occasionally (or even never) needing to break out a “real” build tool like SBT or Mill to do the heavy lifting.

If we agree that this is a place we want to get to, and that these user-facing properties of the Scala language will get us there, the question is: how?

How to Simplify Scala’s getting started experience

To recap, we want to be able to provide good support for Scala programs of varying sizes:

1-liners

1-10 liners

10-1000 liners

1000+ liners

We want people to have a good, supported, official experience using all of them, while having smooth transitions between sizes without needing to spend time uselessly swapping tools, renaming file extensions, or adding boilerplate.

One way we could get there is the following:

Officialize Ammonite as the way to write small Scala programs: amm supports 1-10 liners perfectly fine in the REPL, and supports scripts that go all the way up to 1000ish lines without issue. Transitioning between REPL and Scripts does not need a change of tools: simply copy-paste your REPL command into a file and run amm on that file. Despite ongoing improvements, the scala console and script runner are simply not as good as Ammonite for the majority of purposes, and that doesn’t seem like it’s going to change for the foreseeable future. There are currently a small number of places where Ammonite is inferior to the default Scala REPL. These can be trivially fixed with O(more-than-just-me) people helping out.

Support *.sc files in the project root as main-method entrypoints in SBT and Mill builds: allow *.sc entrypoints to use *.scala “library” files, but not vice versa, via a trivial desugaring that wraps the contents of a *.sc file in a standard def main(args: Array[String]): Unit method. The name of the main method can take after the name of the file The *.sc wrapping could take place either in the build tool, or in the compiler. The end user doesn’t care. The wrapping object could be named arbitrarily (name of file, possibly with package declaration, possibly mangled, or not) since there are no backwards compatibility concerns as *.sc entrypoints do not currently exist SBT would need to support top-level code in the root project as a “standard” way of getting started, rather than starting off people by putting things in a subproject Mill would need to support top-level modules with top-level code in the first place (it currently doesn’t)

As the program grows further, the developer can then break up their SBT/Mill build into subprojects/modules, according to their preferred code organization.

This is just one possible path to get to our desired goal: that any Scala programmer can use a minimal amount of tools as their program scales from a 1-line throwaway, to 10, 100, 1000+ line projects, and should be doable in a relatively short amount of time (1-2 months?). There are likely other paths that could get us there, and if you’re interested in the end goal, I encourage you to write up alternative approaches that can be compared and discussed.

Conclusion

Essentially, this would give us a world where someone getting started with Scala need to know only two tools: amm , and either one of sbt or mill . They would be able to write programs of any size, from 1 to 10s to 100s to 1000s of lines, all of which in a well-supported, official, production-quality environment, without needing to constantly pick between tools which each have their own idiosyncrasies and problems. Especially for the “professional novice” class of potential Scala users, this would make it possible to use Scala effectively “in the small”, as a supplement to whatever their real job or profession is, without ever needing to become deep experts in Scala’s tooling. This should greatly help Scala’s adoption outside the current demographic of professional software engineers.

It turns out, that this is not a lot of work: all these tools already exist, are well-used and stable, well liked, and have existed for years. All we need to do is agree on the same subset of tools and then put in a minimal amount of effort to smooth out the rough edges and transitions between them. In exchange for that a tiny amount of work, we could get a massive improvement to the Scala getting started experience and make it much easier to attract people, experts and non-experts alike, to grow the Scala community.