메인 메서드
메인 메서드
커맨드 라인에서 실행할 수 있는 프로그램을 만들고 싶다면, Scala 3는 이를 위한 새 방법을 제공해요. 메서드에 @main 애노테이션을 달면 그 메서드가 실행 가능한 프로그램의 진입점이 되는 거예요.
본문
한 줄짜리 프로그램 작성하기
Scala 3는 커맨드 라인에서 호출할 수 있는 프로그램을 정의하는 새로운 방법을 제공해요. 메서드에 @main 애노테이션을 추가하면 그 메서드가 실행 가능한 프로그램의 진입점이 돼요.
@main def hello() = println("Hello, World")
이 프로그램을 실행하려면 이 코드 한 줄을 예를 들어 Hello.scala 같은 이름의 파일에 저장하고—파일 이름이 메서드 이름과 일치할 필요는 없어요—scala로 실행하면 돼요.
$ scala run Hello.scala
Hello, World
@main 애노테이션이 붙은 메서드는 최상위(top-level)에 작성하거나(위처럼), 정적으로 접근 가능한 객체 안에 작성할 수 있어요. 어느 쪽이든 프로그램의 이름은 객체 접두어 없이 메서드 이름 그대로예요.
@main 애노테이션에 대해 더 알고 싶다면 아래 절들을 읽거나 영상을 보면 돼요.
커맨드 라인 인자
이 방식에서는 @main 메서드가 커맨드 라인 인자를 처리할 수 있고, 그 인자들은 서로 다른 타입일 수 있어요. 예를 들어 Int, String, 그리고 가변 인자 String* 매개변수를 받는 @main 메서드를 볼게요.
@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)
println(sb.toString)
-- 뒤에 인자를 넘겨주면 돼요.
$ scala run happyBirthday.scala -- 23 Lisa Peter
Happy 23rd Birthday, Lisa and Peter!
보셨듯이 @main 메서드는 매개변수를 원하는 만큼 가질 수 있어요. 각 매개변수 타입마다 인자 String을 해당 타입으로 변환해 주는 scala.util.CommandLineParser.FromString 타입 클래스의 given 인스턴스가 있어야 해요. 또한 보셨듯이 메인 메서드의 매개변수 목록은 커맨드 라인에서 주어진 나머지 인자를 모두 받는 String* 같은 반복 매개변수로 끝날 수 있어요.
@main 메서드로 만든 프로그램은 커맨드 라인에 모든 매개변수를 채우기에 충분한 인자가 있는지, 그리고 인자 문자열이 필요한 타입으로 변환될 수 있는지 검사해요. 검사가 실패하면 프로그램은 오류 메시지와 함께 종료돼요.
$ scala run happyBirthday.scala -- 22
Illegal command line after first argument: more arguments expected
$ scala run happyBirthday.scala -- 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 CommandLineParser.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라는 이름의 클래스를 만든다.- 그 클래스는 Java
main메서드의 흔한 시그니처를 가진 정적 메서드main을 갖는다. 즉Array[String]을 인자로 받아Unit을 반환한다. - 생성된
main메서드는scala.util.CommandLineParser.FromString객체의 메서드를 이용해 변환된 인자로 메서드f를 호출한다.
예를 들어 위의 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에서 객체(object)를 써서 만들어요.
Scala 2와의 하위 호환성
@main 메서드는 Scala 3에서 커맨드 라인에서 호출 가능한 프로그램을 만드는 권장 방법이에요. Scala 2에서의 이전 방식, 즉 App 클래스를 상속하는 object를 만드는 방식을 대체해요.
App의 기존 기능은 "마법 같은" DelayedInit 트레이트에 의존했는데 더 이상 사용할 수 없어요. App은 지금은 제한된 형태로 남아 있지만 커맨드 라인 인자를 지원하지 않고 앞으로 deprecated 될 예정이에요.
프로그램을 Scala 2와 Scala 3 양쪽에서 크로스 빌드해야 한다면, 명시적인 main 메서드와 단일 Array[String] 인자를 가진 object를 쓰는 것을 권장해요.
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라는 파일에 넣으면 앞에서 본 것처럼 scala로 컴파일하고 실행할 수 있어요.
$ scala run happyBirthday.scala -- 23 Lisa Peter
Happy 23rd Birthday, Lisa and Peter!