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

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

I would rather not introduce a new language construct for that specific purpose.

I’m sympathetic to the idea of supporting top-level def definitions, and my suggestion is to change the entry point detection algorithm to support not only top-level objects having a main method taking an Array[String] and returning Unit, but also top-level methods named main (optionally taking an Array[String]). Here are some examples:

// example1.scala
def main = println("hello, world")
// example2.scala
package foo
def main(args: Array[String]) = println(s"hello, ${args(0)}")
// example3.scala
package foo
object Main {
  def main(args: Array[String]) = println(s"hello, ${args(0)}")
}

Invoking scala example1.scala would execute the entry point, if exactly one entry point is found.

Last, the JVM-linker should expose a means of configuring the name of the wrapping object, when the entry point is a top-level main def:

// build.sbt for example2.scala source file
Compile / entryPoint := EntryPoint.topLevelMethod("foo.main", jvmWrappingClass = "foo.Main")

The default would be to use the default wrapping object, which is <empty>$package, foo$package, and foo.Main, in the above examples.

3 Likes

I’m against new keyword. There is no real problem for it and new people in scala should understand pretty quickly ... def main(a:Array[String]):Unit = .... Most of stuff is important while learning and anyone that uses scala needs to understand it anyway in few hours. New keyword (even soft one) should be universal somehow, not special case used once in whole codebase.

But… scripting capabilities are important for anyone that want’s use scala without learning it (lots of people are using js in such way :slight_smile: ). Data Scientist, DevOps, admins and lot of people that just want’s to solve problem RIGHT NOW and they try to do it as fast as possible. They don’t wan’t to learn scala… they wan’t to learn very small subset of language that’ll be helpful.

Here is why amm shines. It is such tool, it allows you to write scripts that are Python-like. My proposition is to not add anything to standard *.scala files and just consider to upgrade *.sc capabilities or bless some stuff from amm as standard scala scripting.
I thing most interesting are:

2 Likes

For easy of starting out, new users, and me for new tasks, I just want to write my code.

println("hi Mum")

This should just work. Frankly, I don’t care if i have to put it into a .sc file to tell the tooling it’s a scala script. I just want this zero biolerplate version to behave. Saying program = bla is boilerplate. It’s there to give the compiler the warm and fuzzies. But I don’t care about the compiler when I’m scripting. I care about it when I write library code. Being able to sit down with a colleague and for us to just start writing down a script, without all the ceremony is a huge win. Particularly if they don’t know scala, and perhaps if they are used to scripting in bash or python.

Hear, hear. Frankly, just in terms of mindshare, this would be fabulous. It seems like a silly little thing to many of us, but AFAICT that little bit of ceremony contributes disproportionately to Scala’s reputation of being more hassle than it’s worth for quick little tasks. And given the number of people who only do “quick little tasks”, that matters.

I’m pretty neutral in terms of exactly where this happens (Ammonite, compiler, whatever), but having an officially blessed (that is, talked up on scala-lang.org), well-supported, easy-to-use approach for zero-overhead scripting that can easily pull in libraries would be great from a “sales” perspective…

1 Like

I’d love to see a real world example of such a “quick little task”, so that I can understand why for some people wrapping their code into a main method is considered such an incredible burden.

And then I would love to see a comparison with writing this in another popular language after you have added the best practice boilerplate in that language.

Suppose I want to list, in alphabetical order, the set of first tokens from lines of a file.

with Open("myfile","r") as f:
    tokens = [line.split()[0] for line in f]
    for i in sorted(list(set(tokens))):
        print(tok)

I can run it in the python interpreter or drop it in a file and python thing.py and it runs. (Well, it should run; something very much like this runs. I didn’t test it.) If I want to switch the file from hard-coded to command line, I add import sys and change the string to sys.argv[1]. Kinda arcane, but super compact.

With my library in Scala, here’s the code I’d enter in the repl:

import kse.eio._
"myfile".file.slurp.foreach{ lines =>
  lines.flatMap(_.split(" ").head).
    toSet.toList.sorted.foreach(println)
}

If I were to make a main function and everything, it would easily double the size (and I have to to get the command-line arg). If I were to add a build file to get the dependency, it would quadruple the size, make me work with twice as many files (up to 5x if you follow “best practices”), and two sets of tools instead of one. If I were to try to take the name of the file as a command-line argument.

So, despite Scala being in code alone (with decent libraries) comparable to Python (widely regarded as a compact powerful language) for tasks like this, Scala is swamped by boilerplate as you try to move from a quick thing you tried out in a REPL to a tiny utility.

1 Like

Thanks for the example. Some thoughts:

(1) The script-ish code is 4 lines, the pre-SIP would be 6 lines, and with main method, it would be 8 lines. True, 8 is double of 4. But is 8 lines of code so terrible?

(2) Is this a real world example? Is there a need out there to “list, in alphabetical order, the set of first tokens from lines of a file”? Just wondering? I have used Python to mung text files. Typically, these are tab-separated, so there is a library (csv), which I import and then I create a reader object. They also have header lines.

(3) As you point out, the real burden is not to write the code, but to specify how to build it, include dependencies, deploy.

(4) Languages like Perl or Python are more oriented towards tasks like processing text files than Java or Scala. Let’s say you want to process text files in Scala with the least effort. You could write a tool that accepts a Scala fragment and puts it into a wrapper that provides not only a main method, but also imports libraries helpful for manipulating Files and Strings, then compiles and runs the resulting Scala file. That would make it truly as easy and powerful as a Perl or Python interpreter. But that wouldn’t be a language feature, but a specialized tool.

Yes, 8 lines is pretty bad. Not only is it wasting half my time, it’s also wasting half my attention attending to things that are completely incidental to the problem I want to solve.

Python, R, Julia, Ruby, Haskell, etc., all don’t do this to you. Rust kinda does, but it has exactly one universal tool (cargo), and it at least writes all the boilerplate for you. Etc…

Very close. I actually needed to pick out the ones that were numbers and list them in numeric order. I could have picked a CSV example instead; this happened not to be CSV.

import $ivy.`com.github.ichoran::kse:0.8.0`, kse.eio._

isn’t much worse if that’s all you have to do. (Yes, having to remember the path is a pain.)

Sure, but you asked for a minimal example, and this one fit. I could have picked adding time intervals instead, or finding how long it takes for a biexponential function to reach 5% of the original value, or listing all the files in a directory that were created last week.

Yeah, that’s exactly the problem. Perl and Python have made their core tooling such that you can use them, unaltered, for many “specialized” cases. You don’t need anything extra; they work for a bunch of stuff out of the box with minimal overhead.

There is no fundamental reason why Scala can’t do the same. There are some tradeoffs that maybe we don’t want to make, but mostly it’s not been a priority to compete on that level, which is a shame.

ad (1) No it isn’t bad, but it discourages writing scripts. Ammonite has best scripting capabilities I’ve ever seen (even better than python in my opinion) and this is the only reason why I’m in favor of be more scripts friendly in native scala. I’ve seen how it could be made well :).

ad (2) I’ve made similar short one shot scripts many times. It is common that you just need to answer “How many times we are using each category in this file?” or “how much money we spend for each project?”. Even if it could be made using few bash commands it is very nice to use language which you know. Here it is not so interesting example but lets assume that you just need to use groupBy once and it become much harder challenge in pure bash.

ad (3) This is more controversial part. Ammonite has it’s magic import $ivy.`organisation::somelib:version` and it works there. This is extreamly powerful tool (See for example play framework server in one file)

ad (4) I’m very happy with processing texts in scala. I’ve made few pretty complicated parsers using Idea worksheets and ammonite scripts. This is place where scala could be used, and even if it is not main target it is nice addition.

Basically, other languages, especially Perl and Python, have fat standard libraries.

You could, for example, use Ammonite and predef a whole bunch of libraries you like, then you wouldn’t have to mention them extra in your scripts.

Does ammonite support import $ivy in predef.sc? Because if you use certain libraries a lot you might add them there.

Yes it does, and yes, that’s what you can do with it.

If someone is on a such early stage to be not able to comprehend a single entry point then probably he would benefit from scripting syntax. What having program keyword solves in his case? If beginner comes from Java then he expects main method with args and he is productive with organizing code into classes. If beginner comes from scripting language then he’ll wonder why do we need to wrap everything in object or class and can’t just execute instructions one by one, which he is extremely used to and finds it natural.

Going for Scala scripting (*.sc instead of *.scala) instead of program keyword will make both guys happy. Java programmer will use his OOP skills in *.scala files, while script programmer will prefer to write *.sc scripts. I think it would be good to give this kind of choice in Scala programming tutorials, instead of weird mix of Javaisms and scriptisms.

4 Likes