Статьи

Scala Tutorial — скриптинг, компиляция, основные методы, возвращаемые значения функций

Предисловие

Это часть 10 учебных пособий для начинающих программистов, попадающих в Scala. Другие посты находятся в этом блоге, и вы можете получить ссылки на эти и другие ресурсы на странице ссылок курса по компьютерной лингвистике, для которого я их создаю. Кроме того, вы можете найти эту и другие серии учебников на странице JCG Java Tutorials .

До этого момента учебные пособия основывались на работе с Scala REPL или на выполнении основных сценариев, запускаемых из командной строки. Последнее называется «скриптингом» и обычно выполняется для довольно простых, автономных задач кодирования. Для более сложных задач, для которых требуется ряд различных модулей и доступ к библиотекам, созданным другими, необходимо работать с системой сборки, которая объединяет ваш код, код других, позволяет вам скомпилировать его, протестировать и упаковать так, что вы можете использовать его в качестве приложения.

Этот учебник поможет вам от запуска скриптов Scala до компиляции программ Scala для создания байт-кода, который может использоваться разными приложениями. Это будет действовать как мост, чтобы настроить вас для следующего шага использования системы сборки. Попутно некоторые пункты будут сделаны об объектах, расширяя некоторые идеи из предыдущего урока об объектно-ориентированном программировании. На высоком уровне актуальность объектов для большей модульной базы кода должна быть достаточно ясной: объекты инкапсулируют данные и функции, которые могут использоваться другими объектами, и мы должны иметь возможность организовать их так, чтобы объекты знали, как найти другие объекты и определения классов. Системы сборки, которые мы рассмотрим в следующем уроке, сделают это просто.

Запуск скриптов Scala

В начале вы начали с REPL.

1
2
scala> println("Hello, World!")
Hello, World!

Конечно, REPL — это просто (очень полезная) площадка для опробования фрагментов кода Scala, а не для реальной работы. Итак, вы увидели, что можете поместить код наподобие println («Hello, World!») В файл с именем Hello.scala и запустить его из командной строки.

1
2
$ scala Hello.scala
Hello, World!

Домашние задания и учебные пособия, сделанные до сих пор, работали таким образом, хотя они немного более сложны. Мы даже можем включить определения классов и объекты, созданные из класса. Например, используя класс Person из предыдущего урока , мы можем поместить весь код в файл с именем People.scala (кстати, это имя не имеет значения — также может быть Blurglecruncheon.scala ).

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Person (
  val firstName: String,
  val lastName: String,
  val age: Int,
  val occupation: String
) {
  
  def fullName: String = firstName + " " + lastName
  
  def greet (formal: Boolean): String = {
    if (formal)
      "Hello, my name is " + fullName + ". I'm a " + occupation + "."
    else
      "Hi, I'm " + firstName + "!"
  }
  
}
  
val johnSmith = new Person("John", "Smith", 37, "linguist")
val janeDoe = new Person("Jane", "Doe", 34, "computer scientist")
val johnDoe = new Person("John", "Doe", 43, "philosopher")
val johnBrown = new Person("John", "Brown", 28, "mathematician")
  
val people = List(johnSmith, janeDoe, johnDoe, johnBrown)
people.foreach(person => println(person.greet(true)))

Теперь это можно запустить из командной строки, что приведет к ожидаемому результату.

1
2
3
4
5
$ scala People.scala
Hello, my name is John Smith. I'm a linguist.
Hello, my name is Jane Doe. I'm a computer scientist.
Hello, my name is John Doe. I'm a philosopher.
Hello, my name is John Brown. I'm a mathematician.

Однако предположим, что вы хотите использовать класс Person из другого приложения (например, определенного в другом файле). Вы можете подумать, что можете сохранить следующее в файле Radiohead.scala , а затем запустить его с помощью Scala.

1
2
3
4
5
6
7
val thomYorke = new Person("Thom", "Yorke", 43, "musician")
val johnnyGreenwood = new Person("Johnny", "Greenwood", 39, "musician")
val colinGreenwood = new Person("Colin", "Greenwood", 41, "musician")
val edObrien = new Person("Ed", "O'Brien", 42, "musician")
val philSelway = new Person("Phil", "Selway", 44, "musician")
val radiohead = List(thomYorke, johnnyGreenwood, colinGreenwood, edObrien, philSelway)
radiohead.foreach(bandmember => println(bandmember.greet(false)))

Однако, если вы выполните « scala Radiohead.scala », вы увидите пять ошибок, каждая из которых жалуется на то, что тип Person не был найден. Как Radiohead.scala может узнать о классе Person и где найти его определение? Я не знаю, как сделать это с помощью Scala-программирования в стиле сценариев, и, хотя я подозреваю, что может быть способ сделать что-то столь простое, мне даже наплевать на это. Давайте сразу перейдем к компиляции.

составление

Обычно мы делаем со Scala компиляцию наших программ в байт-код. Мы не будем вдаваться в подробности этого, но в основном это означает, что Scala превращает текст программы Scala в скомпилированный набор машинных инструкций, которые могут интерпретироваться вашей операционной системой. (На самом деле он компилируется в байт-код Java, что является одной из причин, по которой довольно просто использовать код Java при кодировании в Scala.)

Итак, как выглядит компиляция? Нам нужно начать с изменения кода, который мы сделали выше. Создайте каталог, в котором ничего нет, скажем / tmp / tutorial . Затем сохраните следующее как PersonApp.scala в этом каталоге.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Person (
  val firstName: String,
  val lastName: String,
  val age: Int,
  val occupation: String
) {
  
  def fullName: String = firstName + " " + lastName
  
  def greet (formal: Boolean): String = {
    if (formal)
      "Hello, my name is " + fullName + ". I'm a " + occupation + "."
    else
      "Hi, I'm " + firstName + "!"
  }
  
}
  
object PersonApp {
  
  def main (args: Array[String]) {
    val johnSmith = new Person("John", "Smith", 37, "linguist")
    val janeDoe = new Person("Jane", "Doe", 34, "computer scientist")
    val johnDoe = new Person("John", "Doe", 43, "philosopher")
    val johnBrown = new Person("John", "Brown", 28, "mathematician")
  
    val people = List(johnSmith, janeDoe, johnDoe, johnBrown)
    people.foreach(person => println(person.greet(true)))
  }
  
}

Обратите внимание, что код выглядит довольно похоже на приведенный выше скрипт, но теперь у нас есть объект PersonApp с методом main . Основной метод содержит все то, что имел оригинальный скрипт после определения Person . Также обратите внимание, что в методе main есть аргумент args , который теперь должен выглядеть знакомым. То, что вы видите, это то, что скрипт Scala — это просто упрощенное представление объекта с помощью метода main . Такие сценарии используют соглашение, согласно которому Array [String], предоставленный методу, называется args .

Хорошо, теперь рассмотрим, что произойдет, если вы запустите « scala PersonApp.scala » — вообще ничего. Это потому, что не существует исполняемого кода, доступного за пределами определений объекта и класса. Вместо этого нам нужно скомпилировать код, а затем запустить метод main для конкретных объектов. Следующим шагом является запуск scalac (NB «scala c » с «c», а не «scala») на PersonApp.scala . Название Scalac является сокращением от Scala c ompiler. Выполните следующие шаги в каталоге / tmp / tutorial .

1
2
3
4
5
$ scalac PersonApp.scala
$ ls
Person.class                    PersonApp.class
PersonApp$$anonfun$main$1.class PersonApp.scala
PersonApp$.class

Обратите внимание, что было создано несколько файлов * .class . Это файлы байт-кода, которые может запустить приложение scala. Приятно то, что на этом вся компиляция завершена: когда в прошлом вы запускали «scala» в своих программах (скриптах), она должна была сначала скомпилировать инструкции, а затем запустить программу. Теперь мы разделяем эти этапы на этап компиляции и этап выполнения.

Сгенерировав файлы классов, мы можем запустить любой объект, у которого есть метод main, например PersonApp .

1
2
3
4
5
$ scala PersonApp
Hello, my name is John Smith. I'm a linguist.
Hello, my name is Jane Doe. I'm a computer scientist.
Hello, my name is John Doe. I'm a philosopher.
Hello, my name is John Brown. I'm a mathematician.

Попробуйте запустить « scala Person », чтобы увидеть сообщение об ошибке, которое оно вам дает.

Затем переместите сценарий Radiohead.scala, который вы сохранили ранее, в этот каталог и запустите его.

1
2
3
4
5
6
$ scala Radiohead.scala
Hi, I'm Thom!
Hi, I'm Johnny!
Hi, I'm Colin!
Hi, I'm Ed!
Hi, I'm Phil!

Это тот же сценарий, но теперь он находится в каталоге, содержащем файл Person.class , который сообщает Scala все, что Radiohead.scala необходимо для создания объектов класса Person . Scala делает доступным любой файл класса, определенный в CLASSPATH , переменной среды, которая по умолчанию включает текущий рабочий каталог.

Несмотря на этот успех, мы покидаем сценарий этой публикацией, поэтому измените содержимое Radiohead.scala следующим образом.

01
02
03
04
05
06
07
08
09
10
11
12
13
object RadioheadGreeting {
  
  def main (args: Array[String]) {
    val thomYorke = new Person("Thom", "Yorke", 43, "musician")
    val johnnyGreenwood = new Person("Johnny", "Greenwood", 39, "musician")
    val colinGreenwood = new Person("Colin", "Greenwood", 41, "musician")
    val edObrien = new Person("Ed", "O'Brien", 42, "musician")
    val philSelway = new Person("Phil", "Selway", 44, "musician")
    val radiohead = List(thomYorke, johnnyGreenwood, colinGreenwood, edObrien, philSelway)
    radiohead.foreach(bandmember => println(bandmember.greet(false)))
  }
  
}

Затем запустите scalac для всех файлов * .scala в каталоге. Теперь есть еще файлы классов, соответствующие определенному нами объекту RadioheadGreeting .

1
2
3
4
5
6
7
$ scalac *.scala
$ ls
Person.class                            Radiohead.scala
PersonApp$$anonfun$main$1.class         RadioheadGreeting$$anonfun$main$1.class
PersonApp$.class                        RadioheadGreeting$.class
PersonApp.class                         RadioheadGreeting.class
PersonApp.scala

Теперь вы можете запустить scala RadioheadGreeting, чтобы получить приветствие от участников группы. Обратите внимание, что файл RadioheadGreeting был сохранен и назывался Radiohead.scala, а файлы классов не создавались с именем Radiohead.class и т. Д. Опять же, имя файла можно было назвать совершенно другим, например, Turlingdrome.scala . ( Обними свой внутренний Вогон. )

Несколько объектов в одном файле

Нет проблем с несколькими объектами с основными методами в одном файле. Когда вы компилируете файл с помощью scalac , каждый объект генерирует свой собственный набор файлов классов, и вы вызываете scala для любого файла класса, который содержит определение основного метода, который вы хотите запустить. В качестве примера сохраните следующее как Greetings.scala .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
object Hello {
  def main (args: Array[String]) {
    println("Hello, world!")
  }
}
  
object Goodbye {
  def main (args: Array[String]) {
    println("Goodbye, world!")
  }
}
  
object SayIt {
  def main (args: Array[String]) {
    args.foreach(println)
  }
}

Затем скомпилируйте файл, и затем вы сможете запустить любой из сгенерированных файлов классов (так как все они имеют основные методы).

01
02
03
04
05
06
07
08
09
10
11
$ scalac Greetings.scala
$ scala Hello
Hello, world!
$ scala Goodbye
Goodbye, world!
$ scala Goodbye many useless arguments
Goodbye, world!
$ scala SayIt "Oh freddled gruntbuggly" "thy micturations are to me" "As plurdled gabbleblotchits on a lurgid bee."
Oh freddled gruntbuggly
thy micturations are to me
As plurdled gabbleblotchits on a lurgid bee.

В случае, если вы пропустили его ранее, в массив args входят аргументы командной строки, и вы можете их использовать (или нет, как в случае объектов Hello и Goodbye ).

Функции с возвращаемыми значениями и без

Некоторые функции возвращают значение, а другие нет. В качестве простого примера рассмотрим следующие пары функций.

1
2
3
4
5
scala> def plusOne (x: Int) = x+1
plusOne: (x: Int)Int
  
scala> def printPlusOne (x: Int) = println(x+1)
printPlusOne: (x: Int)Unit

Первый принимает аргумент Int и возвращает Int , которое является значением. Другой получает Int и возвращает Unit , то есть он не возвращает значение. Обратите внимание на разницу в поведении между двумя последующими использованиями функций.

1
2
3
4
5
6
scala> val foo = plusOne(2)
foo: Int = 3
  
scala> val bar = printPlusOne(2)
3
bar: Unit = ()

Scala использует слегка тонкое различие в определениях функций, которые могут отличать функции, которые возвращают значения, от функций, которые возвращают Unit (без значения): если вы не используете знак равенства в определении, это означает, что функция возвращает Unit .

1
2
3
4
5
scala> def plusOneNoEquals (x: Int) { x+1 }
plusOneNoEquals: (x: Int)Unit
  
scala> def printPlusOneNoEquals (x: Int) { println(x+1) }
printPlusOneNoEquals: (x: Int)Unit

Обратите внимание, что приведенное выше определение plusOneNoEquals возвращает Unit , даже если оно выглядит практически идентично ранее определенному plusOne Проверьте это.

1
2
scala> val foo = plusOneNoEquals(2)
foo: Unit = ()

Теперь оглянемся на основные методы, приведенные ранее. Нет равных. Да, они не имеют возвращаемого значения. Они являются точкой входа в ваш код, и любые эффекты запуска кода должны выводиться на консоль (например, с помощью println или через графический интерфейс) или записываться в файловую систему (или где-то в Интернете). Выходы таких функций (те, которые не возвращают значение) называются побочными эффектами. Они нужны вам для основных методов. Однако во многих стилях программирования большая часть работы выполняется с побочными эффектами. Я пытался мягко побудить читателей этого урока принять более функциональный подход, который пытается их избежать. Я сам нашел более эффективный стиль в своем собственном кодировании, так что я надеюсь, что это поможет вам лучше начать с этого момента. (Обратите внимание, что Scala поддерживает множество стилей программирования, что приятно, потому что у вас есть выбор и вы можете выбрать то, что вам больше всего подходит.)

Убираться

Возможно, вы заметили, что каталог, в котором вы работаете, когда вы запускаете scalac для ваших файлов scala, становится довольно замусоренным файлами классов. Например, вот как выглядит состояние каталога кода в этом учебном пособии после компиляции всех файлов.

01
02
03
04
05
06
07
08
09
10
$ ls
Goodbye$.class                          PersonApp.scala
Goodbye.class                           Radiohead.scala
Greetings.scala                         RadioheadGreeting$$anonfun$main$1.class
Hello$.class                            RadioheadGreeting$.class
Hello.class                             RadioheadGreeting.class
Person.class                            SayIt$$anonfun$main$1.class
PersonApp$$anonfun$main$1.class         SayIt$.class
PersonApp$.class                        SayIt.class
PersonApp.class

Беспорядок, верно? Как правило, разработчик Scala редко разрабатывает приложение, компилируя его таким образом. Вместо этого система сборки используется для управления процессом компиляции, организации файлов и обеспечения простого доступа к программным библиотекам, созданным другими разработчиками. Следующий урок расскажет об этом с помощью SBT (Simple Build Tool).

Ссылка: Первые шаги в Scala для начинающих программистов, часть 10 от нашего партнера JCG Джейсона Болдриджа в блоге Bcomposes .

Статьи по Теме :