编写仅Scala 3 适用的一行程序
Scala 3 提供了一种定义可以从命令行调用的程序的新方法:在方法中添加 @main
注释会将其变成可执行程序的入口点:
@main def hello() = println("Hello, world")
只需将该行代码保存在一个名为 Hello.scala 的文件中——文件名不必与方法名匹配——并使用 scala
运行它:
$ scala Hello.scala
Hello, world
@main
注释方法可以写在顶层(如图所示),也可以写在静态可访问的对象中。
在任何一种情况下,程序的名称都是方法的名称,没有任何对象前缀。
学习更多 @main
注解,可以阅读以下章节,或者看这个视频:
命令行参数
使用这种方法,您的@main
方法可以处理命令行参数,并且这些参数可以有不同的类型。
例如,给定这个 @main
方法,它接受一个 Int
、一个 String
和一个可变参数 String*
参数:
@main def happyBirthday(age: Int, name: String, others: String*) =
val suffix = (age % 100) match
case 11 | 12 | 13 => "th"
case _ => (age % 10) match
case 1 => "st"
case 2 => "nd"
case 3 => "rd"
case _ => "th"
val sb = StringBuilder(s"Happy $age$suffix birthday, $name")
for other <- others do sb.append(" and ").append(other)
sb.toString
当你编译该代码时,它会创建一个名为 happyBirthday
的主程序,它的调用方式如下:
$ scala happyBirthday 23 Lisa Peter
Happy 23rd Birthday, Lisa and Peter!
如上所示,@main
方法可以有任意数量的参数。
对于每个参数类型,必须是 scala.util.CommandLineParser.FromString
类型类的一个 given实例,它将参数 String
转换为所需的参数类型。
同样如图所示,主方法的参数列表可以以重复参数结尾,例如 String*
,它接受命令行中给出的所有剩余参数。
从 @main
方法实现的程序检查命令行上是否有足够的参数来填充所有参数,以及参数字符串是否可以转换为所需的类型。
如果检查失败,程序将终止并显示错误消息:
$ scala happyBirthday 22
Illegal command line after first argument: more arguments expected
$ scala happyBirthday sixty Fred
Illegal command line: java.lang.NumberFormatException: For input string: "sixty"
用户自定义的类型作为参数
正如上面指出的,编译器为参数类型寻找 scala.util.CommandLineParser.FromString
类型类 的given 实例。
例如,我们自定义了一个 Color
类型,并希望当以参数使用。你可以像以下代码这样使用:
enum Color:
case Red, Green, Blue
given ComamndLineParser.FromString[Color] with
def fromString(value: String): Color = Color.valueOf(value)
@main def run(color: Color): Unit =
println(s"The color is ${color.toString}")
这对于您程序中的自定义类型以及可能使用的其他库中的类型,都是一样的。
细节
Scala 编译器从 @main
方法 f
生成程序,如下所示:
- 它在有
@main
方法的包中创建一个名为f
的类。 - 该类有一个静态方法
main
,具有 Javamain
方法的通常签名:它以Array[String]
作为参数并返回Unit
。 - 生成的
main
方法调用方法f
并使用scala.util.CommandLineParser
对象中的方法转换参数。
例如,上面的 happyBirthday
方法会生成与以下类等效的附加代码:
final class happyBirthday {
import scala.util.{CommandLineParser as CLP}
<static> def main(args: Array[String]): Unit =
try
happyBirthday(
CLP.parseArgument[Int](args, 0),
CLP.parseArgument[String](args, 1),
CLP.parseRemainingArguments[String](args, 2))
catch {
case error: CLP.ParseError => CLP.showError(error)
}
}
注意:在这个生成的代码中,
<static>
修饰符表示main
方法是作为happyBirthday
类的静态方法生成的。 此功能不适用于 Scala 中的用户程序。 常规“静态”成员在 Scala 中使用对象生成。
与 Scala 2 的向后兼容性
@main
方法是在 Scala 3 中生成可以从命令行调用的程序的推荐方法。
它们取代了 Scala 2 中以前的方法,即创建一个扩展 App
类的 object
:
之前依赖于“神奇”的 DelayedInit
trait 的 App
功能不再可用。
App
目前仍以有限的形式存在,但它不支持命令行参数,将来会被弃用。
如果程序需要在 Scala 2 和 Scala 3 之间交叉构建,建议使用带有 Array[String]
参数的显式 main
方法:
object happyBirthday {
private def happyBirthday(age: Int, name: String, others: String*) = {
... // same as before
}
def main(args: Array[String]): Unit =
happyBirthday(args(0).toInt, args(1), args.drop(2).toIndexedSeq:_*)
}
注意我们用
:_*
来传递不定数量的参数。为了保持向后兼容性,Scala 3 保持了这种用法。
如果将该代码放在名为 happyBirthday.scala 的文件中,则可以使用 scalac
编译它并使用 scala
运行它,如前所示:
$ scalac happyBirthday.scala
$ scala happyBirthday 23 Lisa Peter
Happy 23rd Birthday, Lisa and Peter!
Contributors to this page:
Contents
- 导言
- Scala 3 特性
- 为什么是 Scala 3 ?
- Scala 的味道
- Hello, World!
- The REPL
- 变量和数据类型
- 控制结构
- 领域建模
- 方法
- 头等函数
- 单例对象
- 集合
- 上下文抽象
- 顶层定义
- 总结
- 类型初探
- 字符串插值
- 控制结构
- 领域建模
- 工具
- OOP 领域建模
- 函数式领域建模
- 方法
- 方法特性
- main 方法
- 总结
- 函数
- 匿名函数
- 函数变量
- Eta 扩展
- 高阶函数
- 自定义 map 函数
- 创建可以返回函数的方法
- 总结
- 打包和导入
- Scala 集合
- 集合类型
- 集合方法
- 总结
- 函数式编程
- 什么是函数式编程?
- 不可变值
- 纯函数
- 函数是值
- 函数式错误处理
- 总结
- 类型和类型系统
- 类型推断
- 泛型
- 相交类型
- 联合类型
- 代数数据类型
- 型变
- 不透明类型
- 结构化类型
- 依赖函数类型
- 其他类型
- 上下文抽象
- 扩展方法
- Given 实例和 Using 语句
- 上下文绑定
- Given 导入
- 实现类型类
- 多元相等性
- 隐式转换
- 总结
- 并发
- Scala 工具
- 使用 sbt 构建和测试 Scala 项目
- worksheet
- 与 Java 交互
- 向 Java 开发者介绍Scala
- Scala for JavaScript Developers
- Scala for Python Developers
- 下一步去哪