Программирование на Scala:

Чтобы начать писать приложения Akka с использованием Scala, вам необходимо сначала получить четкое представление о некоторых продвинутых, но легко понятных концепциях языка программирования Scala.

Эти концепции включают, среди прочего; Сопоставление с образцом, полиморфизм (полиморфные методы), черты, классы случаев и закрытые классы.

Соответствие шаблону:

Scala имеет встроенный общий механизм сопоставления с образцом. Это позволяет нам сопоставить любые
данные с политикой первого совпадения.

Вот небольшой пример, который показывает, как сопоставить целочисленное значение:

объект MatchTest1 расширяет приложение {

def matchTest (x: Int): String = x match {

case 1 = ›один
case 2 =› два
case _ = ›много

}

Console.println (matchTest (3))

}

Блок с операторами case определяет функцию, которая отображает целые числа в строки.

Ключевое слово match обеспечивает удобный способ применения функции (например, функции сопоставления с образцом выше) к объекту.

Вот второй пример, который сопоставляет значение с шаблонами разных типов:

объект MatchTest2 расширяет приложение {

def matchTest (x: Any): Any = x match {

case 1 = ›один
case two =› 2
case y: Int = ›scala.Int

}

Console.println (matchTest (два))

}

Первый регистр соответствует, если x относится к целочисленному значению 1. Второй регистр соответствует, если
x равно строке два.

Третий случай представляет собой набранный образец; он сопоставляет
с любым целым числом и связывает значение селектора x с переменной y целочисленного типа.

Оператор сопоставления с образцом в Scala наиболее полезен для сопоставления алгебраических типов
, выраженных через классы case.

Полиморфные методы:

Методы в Scala можно параметризовать как значениями, так и типами.

Как и на уровне класса, параметры-значения заключаются в пару круглых скобок, а параметры типа объявляются в паре скобок.

Вот пример:

объект PolyTest extends Application {

def dup [T] (x: T, n: Int): List [T] =
if (n == 0) Nil
else x :: dup (x, n - 1)

Console.println (dup [Int] (3, 4))
Console.println (dup (three, 3))

}

Метод dup в objectPolyTest параметризован типом T и значениями параметров x: T и n: Int.

Когда вызывается метод dup, программист предоставляет необходимые параметры, от программиста не требуется явно указывать фактические параметры типа
.
Система типов Scala может выводить такие типы.

Это делается путем просмотра
типов заданных параметров значения и контекста, в котором вызывается метод.

Классы дел:

Scala поддерживает понятие case-классов.

Классы case - это обычные классы, которые экспортируют свои параметры конструктора и предоставляют механизм рекурсивной декомпозиции через сопоставление с образцом.

Вот пример иерархии классов, которая состоит из абстрактного суперкласса Term и трех конкретных case-классов Var, Fun и App.

абстрактный класс Term

case class Var (name: String) расширяет Term

case class Fun (arg: String, body: Term) расширяет Term

case class App (f: Срок, v: Срок) расширяет Срок

Чтобы облегчить создание экземпляров класса case, Scala не требует использования нового примитива.

Можно просто использовать имя класса
как функцию.

Вот пример:

Развлечения (x, Fun (y, App (Var (x), Var (y))))

Параметры конструктора классов case обрабатываются как общедоступные значения и могут быть
доступны напрямую.

val x = Var (x)
Console.println (x.name)

Имеет смысл определять классы case только в том случае, если сопоставление с образцом используется для декомпозиции структур данных.

Следующий объект определяет красивую функцию принтера для нашего
представления лямбда-исчисления:

объект TermTest extends Application {

def print (term: Term): Unit = term match {

case Var (n) = ›
Console.print (n)

case Fun (x, b) = ›
Console.print (^ + x +.)
print (b)

case App (f, v) = ›
Console.print (()
print (f)
Console.print ()
print (v)
Консоль. Распечатать())

}

def isIdentityFun (term: Term): Boolean = term match {

case Fun (x, Var (y)), если x == y = ›true

case _ = ›false
}

val id = Fun (x, Var (x))

val t = Fun (x, Fun (y, App (Var (x), Var (y))))

print (t)
Console.println
Console.println (isIdentityFun (id))

Console.println (isIdentityFun (t))
}

В нашем примере функция print выражается как оператор сопоставления с образцом
, начинающийся с ключевого слова match и состоящий из последовательностей предложений case Pattern = ›
Body.

В приведенной выше программе также определена функция isIdentityFun, которая проверяет, соответствует ли данный термин простой функции идентификации.

В этом примере используются глубокие узоры и охранники.

После сопоставления шаблона с заданным значением оценивается защита (определенная после ключевого слова if).

Если он вернет истину, совпадение будет успешным; в противном случае это не удастся, и будет испробован следующий шаблон.

Черты:

Подобно интерфейсам в Java, черты используются для определения типов объектов путем указания подписи
поддерживаемых методов.

В отличие от Java, Scala позволяет частично реализовывать трейты; т.е. для некоторых методов можно определить реализации по умолчанию.

В отличие от классов, трейты могут не иметь параметров конструктора.

Вот пример:

trait Similarity {

def isSimilar (x: Any): Boolean
def isNotSimilar (x: Any): Boolean =! isSimilar (x)

}

Этот трейт состоит из двух методов isSimilar и isNotSimilar.

Хотя isSimilar не предоставляет конкретную реализацию метода (в терминологии Java он является абстрактным), метод isNotSimilar определяет конкретную реализацию.

Следовательно, классы, которые интегрируют эту черту, должны только предоставить конкретную реализацию для isSimilar.

Поведение isNotSimilar наследуется непосредственно от свойства
.

Черты обычно интегрируются в класс (или другие черты) с составом класса миксина:

class Point (xc: Int, yc: Int) расширяет подобие {

var x: Int = xc
var y: Int = yc

def isSimilar (obj: Any) =
obj.isInstanceOf [Point] &&
obj.asInstanceOf [Point] .x == x

}

объект TraitsTest extends Application {

val p1 = новая точка (2, 3)
val p2 = новая точка (2, 4)
val p3 = новая точка (3, 3)

Console.println (p1.isNotSimilar (p2))
Console.println (p1.isNotSimilar (p3))
Console.println (p1.isNotSimilar (2))

}

Вот результат работы программы:

ложь
истина
истина

Запечатанные классы:

Каждый раз, когда вы пишете сопоставление с шаблоном, вы должны убедиться, что охватили все возможные случаи.

Иногда вы можете сделать это, добавив регистр по умолчанию в конце матча, но это применимо только в том случае, если есть разумное поведение по умолчанию.
Что делать, если по умолчанию нет?
Как вы можете чувствовать себя в безопасности,
когда вы прикрыли все дела?

Решение состоит в том, чтобы сделать суперкласс ваших классов дел запечатанным.

К запечатанному классу не может быть добавлено никаких новых подклассов, кроме тех, что находятся в том же файле.
Это очень полезно для сопоставления с образцом, потому что это означает, что вам нужно беспокоиться только о подклассах, о которых вы уже знаете.

Более того, вы также получаете лучшую поддержку компилятора. Если вы сопоставляете классы case, которые наследуются от запечатанного класса, компилятор помечает отсутствующие комбинации шаблонов предупреждающим сообщением.

запечатанный абстрактный класс Expr

case class Var (name: String) расширяет Expr

case class Number (num: Double) расширяет Expr

case class UnOp (operator: String, arg: Expr) расширяет Expr

case class BinOp (operator: String, left: Expr, right: Expr) расширяет Expr

Теперь определите сопоставление с образцом, в котором не учтены некоторые из возможных случаев:

def description (e: Expr): String = e match {

case Number (_) = ›число
case Var (_) =› переменная

}

Вы получите предупреждение компилятора, подобное следующему:

предупреждение: совпадение не является исчерпывающим!

отсутствует комбинацияUnOp
отсутствует комбинацияBinOp

Такое предупреждение сообщает вам, что существует риск того, что ваш код может создать исключение MatchError, поскольку некоторые возможные шаблоны (UnOp, BinOp) не обрабатываются.

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

Однако иногда вы можете столкнуться с ситуацией, когда компилятор слишком разборчив в выдаче предупреждения.

Например, вы могли знать из контекста, что примените описанный выше метод только к выражениям, которые являются либо числами, либо переменными. Итак, вы знаете, что на самом деле
MatchError генерироваться не будет.

Чтобы предупреждение исчезло, вы можете добавить в метод третий универсальный случай, например:

def description (e: Expr): String = e match {

case Number (_) = ›число
case Var (_) =› переменная
case _ = ›throw new RuntimeException // Не должно происходить

}

Это работает, но не идеально.

Вы, вероятно, не будете очень довольны тем, что вам пришлось добавить код, который никогда не будет выполнен (или вы так думаете), просто чтобы компилятор молчал.

Более легкая альтернатива - добавить аннотацию @unchecked к выражению селектора совпадения.

Это делается следующим образом:

def description (e: Expr): String = (e: @unchecked) match {

case Number (_) = ›число
case Var (_) =› переменная

}

В общем, вы можете добавить аннотацию к выражению так же, как вы добавляете тип: после выражения поставьте двоеточие и имя аннотации (которому предшествует знак at).

Например, в этом случае вы добавляете аннотацию @unchecked к переменной e с помощью «e: @unchecked».

Аннотация @unchecked имеет особое значение для сопоставления с образцом.

Если выражение селектора совпадения содержит эту аннотацию,
проверка полноты следующих шаблонов будет подавлена.

Тип варианта:

В Scala есть стандартный тип с именем Option для необязательных значений. Такое значение может быть двух форм.

Это может быть форма Some (x), где x - фактическое значение. Или это может быть объект None, который представляет отсутствующее значение.

Необязательные значения создаются некоторыми стандартными операциями над коллекциями Scala.

Например, метод get в Scala's Map производит Some (value), если было найдено значение, соответствующее данному ключу, или
None, если данный ключ не определен в Map.

Вот пример:

val capitals =
Карта (Франция - ›Париж, Япония -› Токио)

столицы получают Францию

// Option [java.lang.String] =
// Некоторые (Париж)

столицы получают Северный полюс

// Option [java.lang.String] =
// Нет

Наиболее распространенный способ разделения необязательных значений - сопоставление с шаблоном.

Например:

def show (x: Option [String]) = x match {

case Some (s) = ›s
case None =›?

}

Функция запуска:

показывать

// (Option [String]) Строка

шоу (столицы попадают в Японию)

// Строка = Токио

шоу (столицы получают Францию)

// Строка = Париж

шоу (столицы получают Северный полюс)

// String =?

Тип Option часто используется в программах Scala. Сравните это с доминирующей идиомой
в Java, в которой используется значение null для обозначения отсутствия значения.

Например, метод get java.util.HashMap возвращает либо значение, хранящееся в
HashMap, либо null, если значение не найдено.

Этот подход работает для java, но подвержен ошибкам, потому что на практике сложно отслеживать, какие переменные
в программе могут иметь значение NULL.

Напротив, Scala поощряет использование Option для указания необязательного значения.
Такой подход к необязательным значениям имеет несколько преимуществ по сравнению с Java.

Во-первых, для читателей кода гораздо более очевидно, что переменная типа Option [String] является необязательной строкой, чем переменная типа String,
которая иногда может иметь значение null.

Но наиболее важно то, что описанная ранее ошибка программирования, связанная с использованием переменной, которая может иметь значение NULL без предварительной
проверки ее на значение NULL, становится в Scala ошибкой типа.

Если переменная имеет тип Option [String] и вы пытаетесь использовать ее как String, ваша программа Scala не будет компилироваться.

Экстракторы: применить и отменить

Экстрактор в Scala - это объект, одним из членов которого является метод unapply. Цель этого неприменимого метода - сопоставить значение и разобрать его.

Часто объект экстрактора также определяет двойной метод, применяемый для построения значений, но это не обязательно.

Пример:

Объект экстрактора для адресов электронной почты:

object EMail {

// Метод инъекции
// (необязательно)

def apply (user: String, domain: String) = user + @ + domain

// Метод извлечения
// (обязательно)

def unapply (str: String): Option [(String, String)] = {

val parts = str split @

if (parts.length == 2) Some (parts (0), parts (1)) else Нет

}

}

Объект извлечения строки электронной почты.
Этот объект определяет методы применения и отмены. Метод apply имеет то же значение, что и всегда: он превращает EMail в объект, который
может применяться к аргументам в круглых скобках так же, как применяется метод.

Таким образом, вы можете написать электронную почту (John, epfl.ch), чтобы построить строку
[email protected].

Чтобы сделать это более явным, вы также можете позволить EMail наследовать от типа функции Scala, например:

объект EMail extends (String, String) = ›String {...}

... }

Примечание:

Часть «(String, String) =› String »в предыдущем объявлении объекта означает то же самое, что и Function2 [String, String, String], которая объявляет абстрактный метод применения, который реализует EMail.
В результате этого объявления вы можете, например, передать EMail методу
, ожидающему Function2 [String, String, String].

Неприменимый метод - это то, что превращает EMail в экстрактор. В некотором смысле это полностью меняет процесс построения apply. Где apply принимает две строки
и формирует из них строку адреса электронной почты, unapply принимает адрес электронной почты и потенциально возвращает две строки: пользователя и домен адреса.

Но unapply также должен обрабатывать случай, когда данная строка не является адресом электронной почты. Вот почему unapply возвращает тип Option для пар строк.

Результатом будет Some (user, domain), если строка str - это электронное письмо.

Вывод:

Чтобы получить хорошее представление о программировании на Akka, мы создали еще два поста. Первый - это создание протоколов канала сообщений Akka, а второй - создание Akka Actors.

Найдите их здесь:

Протоколы сообщений Akka:

Сопоставление шаблонов с протоколами каналов Akka с чертами Scala и экстракторами

Akka Актеры:

Концепции для начинающих в программировании Akka Actor с помощью Scala

Спасибо!

Спасибо, что прочитали этот пост;
Пожалуйста, найдите меня в;
Twitter: Twitter.com/VakinduPhilliam
Github: Github.com/VakinduPhilliam