Scala Language
Работа с данными в неизменном стиле
Поиск…
замечания
Имена значений и переменных должны быть в нижнем верблюжьем корпусе
Константные имена должны быть в верхнем верблюжьем корпусе. То есть, если член является окончательным, неизменным и принадлежит объекту пакета или объекту, его можно считать константой
Метод, значения и имена переменных должны быть в нижнем верблюжьем корпусе
Источник: http://docs.scala-lang.org/style/naming-conventions.html
Этот компилятор:
val (a,b) = (1,2)
// a: Int = 1
// b: Int = 2
но это не так:
val (A,B) = (1,2)
// error: not found: value A
// error: not found: value B
Это не просто val vs. var
val
и var
scala> val a = 123
a: Int = 123
scala> a = 456
<console>:8: error: reassignment to val
a = 456
scala> var b = 123
b: Int = 123
scala> b = 321
b: Int = 321
- ссылки
val
неизменяемы: какfinal
переменная вJava
, после ее инициализации вы не можете ее изменить - ссылки
var
переназначаются как объявление простой переменной в Java
Неизменяемые и взаимозаменяемые коллекции
val mut = scala.collection.mutable.Map.empty[String, Int]
mut += ("123" -> 123)
mut += ("456" -> 456)
mut += ("789" -> 789)
val imm = scala.collection.immutable.Map.empty[String, Int]
imm + ("123" -> 123)
imm + ("456" -> 456)
imm + ("789" -> 789)
scala> mut
Map(123 -> 123, 456 -> 456, 789 -> 789)
scala> imm
Map()
scala> imm + ("123" -> 123) + ("456" -> 456) + ("789" -> 789)
Map(123 -> 123, 456 -> 456, 789 -> 789)
Стандартная библиотека Scala предлагает как неизменяемые, так и изменяемые структуры данных, а не ссылку на нее. Каждый раз, когда неизменяемая структура данных получает «изменение», создается новый экземпляр вместо изменения первоначальной коллекции на месте. Каждый экземпляр коллекции может иметь значительную структуру с другим экземпляром.
Mutable and Immutable Collection (Официальная документация Scala)
Но я не могу использовать неизменность в этом случае!
Давайте возьмем в качестве примера функцию, которая принимает 2 Map
и возвращает Map
содержащую каждый элемент в ma
и mb
:
def merge2Maps(ma: Map[String, Int], mb: Map[String, Int]): Map[String, Int]
Первая попытка может быть итерирована через элементы одного из карт, используя for ((k, v) <- map)
и как-то вернуть объединенную карту.
def merge2Maps(ma: ..., mb: ...): Map[String, Int] = {
for ((k, v) <- mb) {
???
}
}
Это очень первый шаг немедленно добавить Ограничить: мутацию вне что for
теперь требуется. Это более ясно , когда де-обсахаривания for
:
// this:
for ((k, v) <- map) { ??? }
// is equivalent to:
map.foreach { case (k, v) => ??? }
«Почему мы должны мутировать?»
foreach
полагается на побочные эффекты. Каждый раз, когда мы хотим, чтобы что-то произошло в рамках foreach
нам нужно «что-то побочное», в этом случае мы могли бы мутировать переменный var result
или мы можем использовать изменяемую структуру данных.
Создание и заполнение карты result
Предположим, что ma
и mb
являются scala.collection.immutable.Map
, мы могли бы создать result
Map from ma
:
val result = mutable.Map() ++ ma
Затем итерации через mb
добавляя его элементы, и если key
текущего элемента на ma
уже существует, давайте переопределим его с mb
one.
mb.foreach { case (k, v) => result += (k -> v) }
Мутируемая реализация
Пока что так хорошо, нам «пришлось использовать изменчивые коллекции», и правильная реализация могла бы быть:
def merge2Maps(ma: Map[String, Int], mb: Map[String, Int]): Map[String, Int] = {
val result = scala.collection.mutable.Map() ++ ma
mb.foreach { case (k, v) => result += (k -> v) }
result.toMap // to get back an immutable Map
}
Как и ожидалось:
scala> merge2Maps(Map("a" -> 11, "b" -> 12), Map("b" -> 22, "c" -> 23))
Map(a -> 11, b -> 22, c -> 23)
Складывание на помощь
Как мы можем избавиться от foreach
в этом сценарии? Если все, что нам нужно делать, это в основном перебирать элементы коллекции и применять функцию, тогда как при накоплении результата на опции может использоваться .foldLeft
:
def merge2Maps(ma: Map[String, Int], mb: Map[String, Int]): Map[String, Int] = {
mb.foldLeft(ma) { case (result, (k, v)) => result + (k -> v) }
// or more concisely mb.foldLeft(ma) { _ + _ }
}
В этом случае наш «результат» - это накопленное значение, начиная с ma
, zero
.foldLeft
.
Промежуточный результат
Очевидно, это непреложное решение создает и уничтожает многие экземпляры Map
во время сворачивания, но стоит упомянуть, что эти экземпляры не являются полным клоном Map
накопленной, но вместо этого разделяют значительную структуру (данные) с существующим экземпляром.
Простая разумность
Легче рассуждать о семантике, если она более декларативная, как подход .foldLeft
. Использование неизменяемых структур данных может помочь упростить нашу реализацию.