Skip to content

Commit fce621c

Browse files
committed
Add docs
1 parent a5df0e7 commit fce621c

File tree

3 files changed

+108
-21
lines changed

3 files changed

+108
-21
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
---
2+
layout: doc-page
3+
title: "Main Methods"
4+
---
5+
6+
Scala 3 offers a new way to define programs that can be invoked from the command line:
7+
A @main annotation on a method turns this method into an executable program.
8+
Example:
9+
```scala
10+
@main def happyBirthday(age: Int, name: String, others: String*) = {
11+
val suffix =
12+
(age % 100) match {
13+
case 11 | 12 | 13 => "th"
14+
case _ =>
15+
(age % 10) match {
16+
case 1 => "st"
17+
case 2 => "nd"
18+
case 3 => "rd"
19+
case _ => "th"
20+
}
21+
}
22+
val bldr = new StringBuilder(s"Happy $age$suffix birthday, $name")
23+
for other <- others do bldr.append(" and ").append(other)
24+
bldr.toString
25+
}
26+
```
27+
This would generate a main program `happyBirthday` that could be called like this
28+
```
29+
> scala happyBirthday 23 Lisa Peter
30+
Happy 23rd Birthday, Lisa and Peter!
31+
```
32+
A `@main` annotated method can be written either at the top-level or in a statically accessible object. The name of the program is in each case the name of the method, without any object prefixes. The `@main` method can have an arbitrary number of parameters.
33+
For each parameter type there must be an instance of the `scala.util.FromString` typeclass
34+
that is used to convert an argument string to the required parameter type.
35+
The parameter list of a main method can end in a repeated parameter that then
36+
takes all remaining arguments given on the command line.
37+
38+
The program implemented from a `@main` method checks that there are enough arguments on
39+
the command line to fill in all parameters, and that argument strings are convertible to
40+
the required types. If a check fails, the program is terminated with an error message.
41+
Examples:
42+
```
43+
> scala happyBirthday 22
44+
Illegal command line after first argument: more arguments expected
45+
> scala happyBirthday sixty Fred
46+
Illegal command line: java.lang.NumberFormatException: For input string: "sixty"
47+
```
48+
The Scala compiler generates a program from a `@main` method `f` as follows:
49+
50+
- It creates a class named `f` in the package where the `@main` method was found
51+
- The class has a static method `main` with the usual signature. It takes an `Array[String]`
52+
as argument and returns `Unit`.
53+
- The generated `main` method calls method `f` with arguments converted using
54+
methods in the `scala.util.CommandLineParser` object.
55+
56+
For instance, the `happyBirthDay` method above would generate additional code equivalent to the following class:
57+
```scala
58+
class happyBirthday {
59+
import scala.util.{CommndLineParser => CLP}
60+
<static> def main(args: Array[String]): Unit =
61+
try
62+
happyBirthday(
63+
CLP.parseArgument[Int](args, 0),
64+
CLP.parseArgument[String](args, 1),
65+
CLP.parseRemainingArguments[String](args, 2))
66+
catch {
67+
case error: CLP.ParseError => CLP.showError(error)
68+
}
69+
}
70+
```
71+
**Note**: The `<static>` modifier above expresses that the `main` method is generated
72+
as a static method of class `happyBirthDay`. It is not available for user programs in Scala. Regular "static" members are generated in Scala using objects instead.
73+
74+
`@main` methods are the recommended scheme to generate programs that can be invoked from the command line in Scala 3. They replace the previous scheme to write program as objects with a special `App` parent class. In Scala 2, `happyBirthday` could be written also like this:
75+
```scala
76+
object happyBirthday extends App {
77+
// needs by-hand parsing of arguments vector
78+
...
79+
}
80+
```
81+
The previous functionality of `App`, which relied on the "magic" `DelayedInit` trait, is no longer available. `App` still exists in limited form for now, but it does not support command line arguments and will be deprecated in the future. If programs need to cross-build
82+
between Scala 2 and Scala 3, it is recommende to use an explicit `main` method with an `Array[String] argument instead.
Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package scala.util
22

3+
/** A utility object to support command line parsing for @main methods */
34
object CommandLineParser {
45

56
/** An exception raised for an illegal command line
@@ -8,20 +9,26 @@ object CommandLineParser {
89
*/
910
class ParseError(val idx: Int, val msg: String) extends Exception
1011

11-
/** Parse command line argument `s`, which has index `n`, as a value of type `T` */
12+
/** Parse command line argument `s`, which has index `n`, as a value of type `T`
13+
* @throws ParseError if argument cannot be converted to type `T`.
14+
*/
1215
def parseString[T](str: String, n: Int) given (fs: FromString[T]): T = {
1316
try fs.fromString(str)
1417
catch {
1518
case ex: IllegalArgumentException => throw ParseError(n, ex.toString)
1619
}
1720
}
1821

19-
/** Parse `n`'th argument in `args` (counting from 0) as a value of type `T` */
22+
/** Parse `n`'th argument in `args` (counting from 0) as a value of type `T`
23+
* @throws ParseError if argument does not exist or cannot be converted to type `T`.
24+
*/
2025
def parseArgument[T](args: Array[String], n: Int) given (fs: FromString[T]): T =
2126
if n < args.length then parseString(args(n), n)
2227
else throw ParseError(n, "more arguments expected")
2328

24-
/** Parse all arguments from `n`'th one (counting from 0) as a list of values of type `T` */
29+
/** Parse all arguments from `n`'th one (counting from 0) as a list of values of type `T`
30+
* @throws ParseError if some of the arguments cannot be converted to type `T`.
31+
*/
2532
def parseRemainingArguments[T](args: Array[String], n: Int) given (fs: FromString[T]): List[T] =
2633
if n < args.length then parseString(args(n), n) :: parseRemainingArguments(args, n + 1)
2734
else Nil
@@ -35,21 +42,3 @@ object CommandLineParser {
3542
println(s"Illegal command line$where: ${err.msg}")
3643
}
3744
}
38-
39-
/* A function like
40-
41-
@main f(x: S, ys: T*) = ...
42-
43-
would be translated to something like
44-
45-
import CommandLineParser._
46-
class f {
47-
@static def main(args: Array[String]): Unit =
48-
try
49-
f(
50-
parseArgument[S](args, 0),
51-
parseRemainingArguments[T](args, 1): _*
52-
)
53-
catch case err: ParseError => showError(err)
54-
}
55-
*/
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
@main def happyBirthday(age: Int, name: String, others: String*) = {
2+
val suffix =
3+
(age % 100) match {
4+
case 11 | 12 | 13 => "th"
5+
case _ =>
6+
(age % 10) match {
7+
case 1 => "st"
8+
case 2 => "nd"
9+
case 3 => "rd"
10+
case _ => "th"
11+
}
12+
}
13+
val bldr = new StringBuilder(s"Happy $age$suffix birthday, $name")
14+
for other <- others do bldr.append(" and ").append(other)
15+
bldr.toString
16+
}

0 commit comments

Comments
 (0)