这个地方基本没有任何文档记录,在学习的过程中屡屡碰壁,因此记录一下这部分的内容,懒得看可以直接跳到总结看结论。
踩坑步骤
首先来看看书上让我们怎么写:
//main.scala
object Start {
def main(args:Array[String]) = {
try {
val score = args(1).toInt
val s = Students2(args(0), score)
println(s.toString)
} catch {
case ex: ArrayIndexOutOfBoundsException => println("Arguments are deficient!")
case ex: NumberFormatException => println("Second argument must be a Int!")
}
}
}
之后,书上说我们执行下面的编译和运行指令即可运行:
scala students2.scala Start.scala
scala Start Tom
事实上,我们会获得以下报错:
jia@J-MateBookEGo:~/scala_test$ scala Start Tom
[error] Start is not a scala sub-command and it is not a valid path to an input file or directory.
Try viewing the relevant help to see the list of available sub-commands and options.
scala --help
Tom: input file not found
Try viewing the relevant help to see the list of available sub-commands and options.
scala --help
经过检查,已经生成了一系列.class的JVM可执行文件,但似乎scala拒绝执行。
但是出于严谨,我们需要考虑是否是我们的代码编写有问题,因此我们来到scala的官网:
main 方法 | Scala 3 — Book | Scala Documentation
果然,官网在这里提示我们建议使用Scala3的全新语法,它将不需要创建一个object和输入参数列表,而是使用更简洁的语法来完成,这在我们之前的debug中已经体验过,当需要传入参数时,官网的程序示例是这样的:(当然,官网表示针对Scala2的写法仍然支持,就是前面传入参数列表的写法)
@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
然后官网说执行如下编译后运行指令可以得到结果:
$ scala happyBirthday 23 Lisa Peter
Happy 23rd Birthday, Lisa and Peter!
实际上,它又报错了,和之前是一样的:
jia@J-MateBookEGo:~/scala_test$ scala happyBirthday 23 Lisa Peter
[error] happyBirthday is not a scala sub-command and it is not a valid path to an input file or directory.
Try viewing the relevant help to see the list of available sub-commands and options.
scala --help
23: input file not found
Try viewing the relevant help to see the list of available sub-commands and options.
scala --help
Lisa: input file not found
Try viewing the relevant help to see the list of available sub-commands and options.
scala --help
Peter: input file not found
Try viewing the relevant help to see the list of available sub-commands and options.
scala --help
根据之前debug的经验,这应该是由于编译器版本更新导致的。我们在之前的文章中使用新的执行指令解决了这个问题,它要求我们显式写出run:前情提要(没错,这个官网仍然是错的)
于是我们尝试使用这条指令运行:
jia@J-MateBookEGo:~/scala_test$ scala run -cp . -M happyBirthday
Illegal command line: more arguments expected
非常好,现在这个程序成功运行了,但如果想要传入参数,它又报错了
jia@J-MateBookEGo:~/scala_test$ scala run -cp . -M happyBirthday 22
[error] 22: input file not found
Try viewing the relevant help to see the list of available sub-commands and options.
scala run --help
那么我们现在百思不得其解,是否是这种写法不再被支持了?网上无法找到任何资料。万念俱灰之时,我们又找到了github这个提问Document starting programs compiled by scalac in the current working directory · Issue #3132 · VirtusLab/scala-cli · GitHub
根据回答的内容,我们顺藤摸瓜找到了Scala CLI的官网,没错,这次终于找到罪魁祸首了:
Migrating from the old Scala runner | Scala CLI
在这里说明,
从SIP-46开始,Scala CLI已被接受为新的Scala命令。
在这种情况下,本指南的目的是强调旧的scala脚本和scala CLI之间的主要区别,以使用户尽可能顺利地进行迁移。
因此,我们之前的命令很多都不再被支持,这是导致错误的根本原因。但是Scala的官网并没有在这里更新使用Scala CLI的指令。
当我们从官网下载最新的Scala,Scala CLI已经作为原生指令被提供,也就是说我们现在使用的scala指令就是以前的scala-cli。
那么我们能不能仍然使用以前的编译器呢,答案是可以,但我们需要使用指令scala_lagacy,这里说明如下
是否可以使用旧的Scala runner?
是的,尽管它的用法已被弃用,但在scala_legacy命令下仍然可以使用它。然而,它很可能在未来的版本中被删除。
那我们进行测试,看看最新版本是否仍然支持:
jia@J-MateBookEGo:~/scala_test$ scala_legacy -version
scala_legacy: command not found
效率真高啊,说删就删,这种完全牺牲针对旧版本的兼容性的操作令人费解。但也没办法,看来我们只能学习新的指令,看看他们有什么区别,此处我们直接引用一段官网的内容:
老方法
在旧的运行程序中,第一个参数被视为输入源,而第二个及之后的参数被视为程序参数。
@main def main(args: String*): Unit = println(args.mkString(" "))
scala_legacy Source.scala programArg1 programArg2
由于第一个参数之后的所有内容都必须作为程序参数任意读取,而不管格式如何,因此必须在源输入之前传递所有运行程序选项。可以使用下面的语句按照老方法执行:
scala_legacy -save Source.scala programArg1 programArg2
很好,这就是我们尝试使用的方案。显然,他们已经彻底删除了这个针对老方法的支持。 那么新方法是什么?
Scala CLI的使用方法
使用Scala CLI处理参数的默认方式,输入和程序参数必须加上--。两者的数量都没有限制。
def placeholder = println("Example extra source")
scala Source.scala Source2.scala -- programArg1 programArg2
此外,可以在输入部分之前传递一个Scala CLI子命令。例如,要调用上面的例子,显式地指定run子命令,像这样传递它:
scala run Source.scala Source2.scala -- programArg1 programArg2
Runner选项可以传递到输入部分的任何位置(在--之前)。例如,以下所有示例都是将Scala版本显式指定为3.2的正确方法
scala -S 3.2 Source.scala Source2.scala -- programArg1 programArg2
scala Source.scala -S 3.2 Source2.scala -- programArg1 programArg2
scala Source.scala Source2.scala -S 3.2 -- programArg1 programArg2
好的,我们试一下执行书上的代码,果然成功了。
jia@J-MateBookEGo:~/scala_test$ scala Start.scala students2.scala -- Tom
Arguments are deficient!
jia@J-MateBookEGo:~/scala_test$ scala Start.scala students2.scala -- Tom aaa
Second argument must be a Int!
jia@J-MateBookEGo:~/scala_test$ scala Start.scala students2.scala -- Tom 100
Tom's score is 100.
另外这个网页也展示了一些别的信息,主要是关于版本的变更导致一些语法不被支持。
比如以下这个代码:
println(args.mkString(" "))
它在3.x的版本中不再支持。
scala script.scala -- Hello world
[error] ./ScriptInScala.scala:1:1
[error] Illegal start of toplevel definition
[error] println(args.mkString(" "))
[error] ^^^^^^^
Error compiling project (Scala 3.2.2, JVM)
Compilation failed
像2.x版本中的object和参数列表法定义main函数仍然支持,但推荐使用@main方法。
总结
Start.scala中的代码:
//Start.scala
object Start {
def main(args:Array[String]) = {
try {
val score = args(1).toInt
val s = Students2(args(0), score)
println(s.toString)
} catch {
case ex: ArrayIndexOutOfBoundsException => println("Arguments are deficient!")
case ex: NumberFormatException => println("Second argument must be a Int!")
}
}
}
students2.scala中的代码:
class Students2(val name:String, var score: Int) {
def apply(s:Int) = score = s
def display() = println("Current core is " + score + ".")
override def toString = name + "'s score is '" + score + "."
}
object Students2 {
def apply(name: String, score: Int) = new Students2(name, score)
}
编译和执行命令:(别用scalac了,因为官网都没写scalac后如何执行)
jia@J-MateBookEGo:~/scala_test$ scala Start.scala students2.scala -- Tom
Arguments are deficient!
jia@J-MateBookEGo:~/scala_test$ scala Start.scala students2.scala -- Tom aaa
Second argument must be a Int!
jia@J-MateBookEGo:~/scala_test$ scala Start.scala students2.scala -- Tom 100
Tom's score is 100.