Scala 3 — Book

Main методы в Scala 3

Language
Написание однострочных программ только в 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 и дополнительные строковые параметры:

@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)

После компиляции кода создается основная программа с именем happyBirthday, которая вызывается следующим образом:

$ scala happyBirthday 23 Lisa Peter
Happy 23rd Birthday, Lisa and Peter!

Как показано, метод @main может иметь произвольное количество параметров. Для каждого типа параметра должен существовать given экземпляр класса типа scala.util.CommandLineParser.FromString, который преобразует аргумент из 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 для типа аргумента. Например, предположим, что у вас есть собственный тип 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 следующим образом:

  • он создает класс с именем f в пакете, где был найден метод @main.
  • класс имеет статический метод main с обычной сигнатурой Java main метода: принимает Array[String] в качестве аргумента и возвращает Unit.
  • сгенерированный main метод вызывает метод f с аргументами, преобразованными с помощью методов в объекте scala.util.CommandLineParser.FromString.

Например, приведенный выше метод 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. Они заменяют предыдущий подход, который заключался в создании object, расширяющего класс App:

Прежняя функциональность App, основанная на “волшебном” DelayedInit trait, больше недоступна. App все еще существует в ограниченной форме, но не поддерживает аргументы командной строки и будет объявлен устаревшим в будущем.

Если программам необходимо выполнять перекрестную сборку между Scala 2 и Scala 3, вместо этого рекомендуется использовать object с явным методом main и одним аргументом Array[String]:

object happyBirthday {
  private def happyBirthday(age: Int, name: String, others: String*) = {
    ... // тоже, что и раньше
  }
  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: