Введение в Scala

Основные типы. Ввод и вывод. Управляющие операторы

Кафедра ИВТ и ПМ. Ветров С. В.

2023


Scala 2: Hello, World!


		object Program {
			def main(args: Array[String]) = {
			  println("Hello, World!")
			}
		  }
	

Scala 3: Hello, World!


@main
def main(): Unit = {
  	println("Hello")
	  println("world!")
}
	

Scala 3: Hello, World!


	@main
	def main() =
	    println("Hello")
	    println("world!")

	
Синтаксис без операторных скобок, с отступами (в стиле Python) и без указания возвращаемого типа функции.

Hello, World!


	@main
	def main() =
	    println("Hello world!")
	    println("Hello world!")
	
  • @main — аннотация, помечающая текущий метод как главную функцию программы. Тело функции начинается после =. Фигурные скобки вокруг тела писать необязательно.

Hello, World!


	@main
	def main() =
	    println("Hello world!")
	    println("Hello world!")
	
  • @main — аннотация, помечающая текущий метод как главную функцию программы. Тело функции начинается после =. Фигурные скобки вокруг тела писать необязательно.
  • Если фигурных скобок нет, то вложенность определяют отступы (как в Python).
  • В стандарте Scala 2 нужно создать класс наследник от App, который будет включать основной код.
## Компиляция и запуск ```scala @main def main_method() = println("Hello world!") ``` ```text[1-2|5-14|16-18] # Компиляция scalac main.scala # многие параметры компилятора, например classpath, похожи на аналогичные для javac # Результат: main.scala # class файлы для каждого пакета, класса, метода main$package$.class main$package.class main_method.class # TASTy = Typed Abstract Syntax Trees main$package.tasty main_method.tasty # Запуск, нужно указать только имя главного метода scala main_method ```
## Запуск без компиляции ```scala @main def main_method() = println("Hello world!") ``` Запуск без компиляции ```bash scala main.scala ```
## Сборщик SBT **sbt** — Simple Build Tool ```bash sbt --version ``` Сборщик полагается на заранее созданные файлы: - `build.sbt` - Пример содержимого: `scalaVersion := "3.2.2"` - `project\bildp.roperties` - Пример содержимого: `sbt.version=1.6.1` Запуск сборщика в корневой папке проекта, файл сборки будет найден автоматически ```bash sbt compile ``` Проект будет скомпилирован в папку `target`. Например, запуск jar файла проекта: `scala target/scala-3.2.2/sbt_example_3-0.1.0-SNAPSHOT.jar`
## Сборщик SBT sbt может создавать проекты на основе шаблонов, например ```bash sbt new scala/scala-seed.g8 sbt new playframework/play-scala-seed.g8 sbt new akka/akka-http-quickstart-scala.g8 sbt new http4s/http4s.g8 sbt new holdenk/sparkProjectTemplate.g8 ``` Другие команды: - `compile` - `run` - `clean` - `package` — создать jar файл из исходников (зависимости отдельно) - `test` - `update` — скачать зависимости (помещаются в папку пользователя, например на linux: `~/.cache/coursier/v1`) [[get-coursier.io/docs/cache](https://get-coursier.io/docs/cache)] Пример: [Scala/examples/sbt_example](https://github.com/ivtipm/BigDataLanguages/tree/main/Scala/examples/sbt_example) Подробнее о sbt: [https://docs.scala-lang.org/scala3/book/tools-sbt.html](https://docs.scala-lang.org/scala3/book/tools-sbt.html) Документация: [scala-sbt.org](https://www.scala-sbt.org/index.html)
## Сборщик sbt Минимальная структура проекта с тестами: ```text . ├── build.sbt ├── src/ │ ├── main/ │ │ └── scala/ │ └── test/ │ └── scala/ └── target/ ``` Каталог сборки `.`, sbt автоматически находит все файлы исходного кода в указанной структуре проекта.
## Сборщик sbt Более полная структура: ```text . ├── build.sbt ├── project/ │ └── build.properties ├── src/ │ ├── main/ │ │ ├── java/ │ │ ├── resources/ │ │ └── scala/ │ └── test/ │ ├── java/ │ ├── resources/ │ └── scala/ └── target/ ``` - `sbt compile` - `sbt test` - `sbt run`

Интерактивный режим

  • REPL = Read Eval Print Loop
  • Интерактивный режим как в скриптовых языках
  • Запуск: scala

## Интерактивный режим Обращение к командной оболочке ОС из scala REPL ```scala import sys.process._ // вызов команды "ls -al".! ```
## Аргументы командной строки ```scala @main def main(filename: String) = // программа с одним параметром (имя текущего файла не считается) // набор параметров функции = минимально необходимый набор аргументов командной строки println( filename) ``` Запуск (с интерпретатором) ```bash scala main.scala my_filename.txt ``` Запуск с sbt ``` sbt "run my_file.txt" ``` Аргументы могут иметь произвольный тип, до тех пор пока есть стандартный способ получить их из строкового типа Подробнее: [docs.scala-lang.org/scala3/book/methods-main-methods.html#command-line-arguments](https://docs.scala-lang.org/scala3/book/methods-main-methods.html#command-line-arguments) См. также: [https://github.com/scopt/scopt](https://github.com/scopt/scopt) — библиотека для разбора аргументов командной строки

Пакеты

  • Почти аналогичны пакетам Java
  • Имя пакета не обязательно должно совпадать с именем папок, в которые файл пакета вложен


Объявление


package my_package

// my_code

Подключение


	// подключить всё пространство имён Math
	import Math._    		// Scala 2+
	import Math.* 			// Scala 3

	import Math.exp

	// подключение нескольких имён отдельно
	import Math.{PI, E}
	

// подключаются автоматически
java.lang.*
scala.*

Система типов

  • Статическая типизация
  • Местами строгая (нельзя без явного преобразования
  • 
    val x: Int = 2.3 // ошибка: требуется явное преобразование в Int
    
  • Возможен автоматический вывод типа (inference) для многих ситуаций.\ Например
    
    val y: = 40+2; 	// Int
    
  • Самый базовый тип - надтип
  • Производный тип - подтип
Общий синтаксис объявления переменной или константы

val|var name: type = value

Константа (immutable variable)


val my_const:Float = 333/106;
val my_const = 333/106;					// автоматический вывод типа: Double
val my_const;									  // Ошибка: значение не задано

Переменная


var my_variable:Int = 0;
var my_variable = 0;
var my_variable:Int;	// Ошибка: не указано начальное значение
var my_variable;			// Ошибка: невозможно вывести тип
  • Тип у переменных указывать тоже не обязательно, но рекомендуется.
  • Используйте константы всюду, где это возможно

Числовые типы (value типы)


	val b: Byte = 1
	val i: Int = 1 				// default for type inference
	val l: Long = 1
	val s: Short = 1
	val d: Double = 2.0 	// default for type inference
	val f: Float = 3.0e2  // 3.0 * 10 ^ 2
	

Строки

Такие же как в Java

val S0 = "I'am string"

// многострочный литерал
val quote = """The essence of Scala:
               Fusion of functional and object-oriented
               programming in a typed setting."""

// многострочный литерал, без пробелов вначале
val quote = """The essence of Scala:
               |Fusion of functional and object-oriented
               |programming in a typed setting.""".stripMargin

	

Строки

Интерполяция

s, f и raw - префиксы для строк

s - префиксы строки с интерполяцией


val name = "James"
println( s"Hello, $name" )  // Hello, James	

// интерполяция с выражениями внутри {}
println( s"5 = ${2+2}" )  // Hello, James	

// экранирование знака доллара
println( s"price = $$99.5" )  // price = $99.5
	

Строки

Интерполяция и форматирование

f - префиксы строки с интерполяцией и форматированием

Параметры форматирование записываются после знака %


val height = 1.9d
val name = "James"
println(f"$name%s is $height%2.2f meters tall")  // James is 1.90 meters tall
	

Документация о форматировании в Java

Строки

Интерполяция строк как есть (сырых строк)

raw - интерполяция аналогичная s, но без служебных символов в строке


s"a\nb"

// Результат:
a
b


raw"a\nb"

// Результат:
a\nb
> ## Строки. Документация - `.toInt` - `.toDouble` - `.size` - `.split(Char)` - ... [scala-lang.org/api/3.2.2/scala/collection/StringOps.html#](https://www.scala-lang.org/api/3.2.2/scala/collection/StringOps.html)

Регулярные выражения

Создание регулярного выражения из строки

import scala.util.matching.Regex

// метод строки r создаст из неё экземпляр типа numberPattern
val number_r: Regex = "[0-9]".r

// Для записи метасимволов удобно использовать префикс raw
val time_r: Regex = raw"\d\d:\d\d".r
		

Регулярные выражения

Проверка соответствия строги регулярному выражению

import scala.util.matching.Regex

// метод строки r создаст из неё экземпляр типа numberPattern
val number_r: Regex = "[0-9]".r

  
// проверка на полное соответствие
number_r.matches("7");      // true
number_r.matches("71");     // false
number_r.matches("number"); // false
		

Регулярные выражения

Поиск всех совпадений

import scala.util.matching.Regex

// метод строки r создаст из неё экземпляр типа numberPattern
val number_r: Regex = "[0-9]".r

// number_r.findAllIn("12 abc 3 8") -> итератор
for m <- number_r.findAllIn("12 abc 3 8") do
	  println(m)
	

Вывод


1
2
3
8
	

Возвращаемый итератор имеет метод .toList

Больше примеров

scala-lang.org/tour/regular-expression-patterns.html#inner-main scala-lang.org/api/3.2.2/scala/util/matching/Regex.html#

Вывод


println("Hello, World!")
print("Hello, World!")

Форматные строки


val p = 333.0 / 106            // 3.141509433962264
println( f"pi ≈ $p%7.3f" )
println( f"pi ≈ ${333.0/106}%7.3f" )

Вывод


pi ≈   3.143
pi ≈   3.143

Ввод


// подключение трёх функций из пакета 
import scala.io.StdIn.{readLine, readInt, readLong}

val s: String = readLine()
	

Управляющие операторы

  • if/then/else
  • отсутствует return
  • for loops
  • while loops
    • break и continue отсутствуют
    • чаще вместо while используется рекурсия
  • try/catch/finally
  • for expressions — генераторы
  • match expressions — сопоставление с образцом

IF


if x < 0 then
  println("negative")
else if x == 0 then
  println("zero")
else
  println("positive")
	

IF

  • считается выражением

val minValue = if a < b then a else b
	

IF


import scala.util.Random
val rand = Random
val a = rand.nextInt()
val b = rand.nextInt()
val max = 
    if (a > b) a 
    else b
	

match — сопоставление с образцом


// `i` is an integer
val day = i match
  case 0 => "Sunday"
  case 1 => "Monday"
  case 2 => "Tuesday"
  case 3 => "Wednesday"
  case 4 => "Thursday"
  case 5 => "Friday"
  case 6 => "Saturday"
  case _ => "invalid day"   // the default, catch-all
	

match

ИЛИ


val day = i match
  case 0 | 6 => "Sunday or Saturday"
  case 1 => "Monday"
  case 2 => "Tuesday"
  case 3 => "Wednesday"
  case 4 => "Thursday"
  case 5 => "Friday"
  case _ => "invalid day"   // the default, catch-all
	

match

Условие внутри case


import scala.util.Random

val x: Int = Random.nextInt(10)

println(x)

x match
  case x if (x%2==0) => "even"
  case 1 => "one"
  case 3 => "three"
  case _ => "other"
	

match

Извлечение значения

Вместо значения в case можно указать переменную, в которую это значение запишется. Так как переменная будет всегда равна проверяемому значению, такая ветка будет срабатывать всегда.


val day = i match
  case 0 => "Sunday"
  case 1 => "Monday"
  case 2 => "Tuesday"
  case 3 => "Wednesday"
  case 4 => "Thursday"
  case 5 => "Friday"
  case 6 => "Saturday"
	case x => s"$x - not a valid day number" // the default, catch-all
	// x - локальная для case переменная, 
	// в которую извлекается проверяемая переменная
	

match

Извлечение значения объекта


case class Email(sender: String, title: String, body: String)

// возможные варианты для сопоставляемой переменной:
// val x = new Email("Spammer", "Not a Spam", "Hello, dear friend")
val x = new Email("Manager", "important!!!!", "Hello, dear friend")          
// val x = new Email("Manager2", "important!!!!", "Hello, dear friend")
// val x = new Email("Vasya", "qwerty", "Hello, dear friend")  

// 2 или более восклицательных знака среди всего остального, 
// выражение должно быть в скобках для обозначения места для подстановки строки
val title_reg   = "(.*!{2,}.*)".r   
// 2 или более восклицательных знака среди всего остального
val title_reg22 =  ".*!{2,}.*".r     

val action = x match
	// проверка по полю sender, с извлечением значения из поля title 
	// в переменную title1
	case Email("Spammer", title1, _)    	=> s"send <$title1> to spam"
	// проверка по последнему полю
	case Email(_, _, "money")                  		=> "mark important"
	// проверка по точному совпадению с первым полем, 
	// извлечение значения второго поля в title22, 
	// проверка по регулярному выражению
	case Email("Manager", title_reg(title22), _) 	=> "delete"
	// значение для проверки в регулярном выражении можно и не извлекать
	// в переменную, ведь и так понятно что туда подставлять
	case Email("Manager2", title_reg22(), _) 			=> "also delete"
	// если ничего не сработало
	case _ => "do nothing"
	

FOR

Цикл со счётчиком

		for i <- 1 to 10 do 
			  println(i)
			
i <- ints - генератор

Совместный цикл (цикл по коллекции)

		val ints = List(1, 2, 3, 4, 5)

		for i <- ints do 
		    println(i)
			

FOR

  • Заголовок цикла по коллекции может включать условия (guards)
  • Заголовок можно записывать в несколько строк

val ints = List(1, 2, 3, 4, 5)

for
  i <- ints
  if i % 2 == 0
do
  println(i)
		

Вывод


2
4
		

FOR

  • Для записи вложенных циклов достаточно одного оператора for, но нужно несколько итераторов и генераторов

for
  i <- 1 to 3
  j <- 'a' to 'c'
do
  println(s"i = $i, j = $j")  
		

Вывод


i = 1, j = a
i = 1, j = b
i = 1, j = c
i = 2, j = a
i = 2, j = b
i = 2, j = c
i = 3, j = a
i = 3, j = b
i = 3, j = c
		

FOR

  • Условия можно помещать и во вложенных циклах
  • В примере ниже они будут проверяться каждую итерацию самого вложенного цикла

for
  i <- 1 to 10
  j <- 'a' to 'c'
  if i % 2 == 0
  if j != 'b'
do
  println(s"i = $i, j = $j")  
		

Вывод


i = 2, j = a
i = 2, j = c
i = 4, j = a
i = 4, j = c
i = 6, j = a
i = 6, j = c
i = 8, j = a
i = 8, j = c
i = 10, j = a
i = 10, j = c
		

Выражения на основе for (генераторы)

    Принцип работы похож на list comprehension из Python

val ints = List(1, 2, 3, 4, 5)

// Создание списка
val doubles = for i <- ints yield i * 2

// Результат List[Int] = 
List(2, 4, 6, 8, 10)
		

Такая запись удобна для преобразования списков


val names = List("chris", "ed", "maurice")
val capNames = for name <- names yield name.capitalize

// Результат List[String] =
List(Chris, Ed, Maurice)
		
Например: while используется редко, т.к. есть рекурсия. break и continue отсутствуют (как и return в середине функции). do ... while не используется. for for ( i <- 1 to 10 ) println(i) // в обратном порядке for ( i <- 10 to 1 by -1) println(i) // цикл по коллекции for ( i <- Vector(1,2,3)) println(i) Можно использовать внутри конструкции, похожей как в list comprehension, в том числе с условием. println( for (i <- 1 to 10) yield i*i) // 1, 4, 9, ... // квадраты чисел кратных трём println( for (i <- 1 to 100 if i%3 == 0) yield i*i ) // 9, 36, 81, 144 Можно указывать произвольное число действий в заголовке цикла. Это создаст вложенный цикл: for ( i <- 1 to 10; j <- 1 to 10) print(f"$i,$j ") Код с фигурными скобками вместо круглых можно записать в несколько строк, не разделяя операторы точкой с запятой. for { i <- 1 to 10 j <- 1 to 10 } print(f"$i,$j; ") В тело цикла можно включить условие, контролирующее изменение счётчика for { i <- 1 to 5 j <- 1 to 5 if i >= j} print(f"$i,$j; ") Вывод 1,1; 2,1; 2,2; 3,1; 3,2; 3,3; 4,1; 4,2; 4,3; 4,4; 5,1; 5,2; 5,3; 5,4; 5,5;
## Assert ```scala assert( 4 == 2+2 ) ```
## Стандартная библиотека `scala.` - [`math`](https://scala-lang.org/api/3.x/scala/math.html#) - [`cllection`](https://scala-lang.org/api/3.x/scala/collection.html#) - [`io`](https://scala-lang.org/api/3.x/scala/io.html#) - `object StdIn` - `object Source` - [`sys`](https://scala-lang.org/api/3.x/scala/sys.html#) — methods for reading and altering core aspects of the virtual machine as well as the world outside of it - [`util`](https://scala-lang.org/api/3.x/scala/util.html#) - `matching` - `class Regex` - `class Random` - [`jdk`](https://scala-lang.org/api/3.x/scala/jdk.html#) — utilities to interact with JDK classes - [`concurrent`](https://scala-lang.org/api/3.x/scala/concurrent.html#) — concurrent and parallel programming - ...
## Продолжение [Scala_oop_func_collections.html](Scala_oop_func_collections.html)