Простота незначительных обновлений
Одной из причин использования изменчивости является отслеживание какого-либо текущего процесса.Например, предположим, что я редактирую большой документ и у меня есть сложный набор классов для отслеживания различных элементов текста, истории редактирования, положения курсора и так далее.Теперь предположим, что пользователь нажимает на другую часть текста.Нужно ли заново создавать объект документа, копируя много полей, но не поле EditState
;воссоздать EditState
с новыми ViewBounds
и documentCursorPosition
?Или я могу изменить изменяемую переменную в одном месте? Пока безопасность потоков не является проблемой , тогда гораздо проще и менее подвержено ошибкам просто обновить переменную или две, чем копировать все.Если безопасность потока является проблемой, то защита от одновременного доступа может оказаться более трудоемкой, чем использование неизменяемого подхода и обработка устаревших запросов.
Эффективность вычислений
Еще одна причина использовать изменчивость для скорости.Создание объекта обходится дешево, но простые вызовы методов обходятся дешевле, а операции с примитивными типами еще дешевле.
Предположим, например, что у нас есть карта и мы хотим суммировать значения и квадратызначения.
val xs = List.range(1,10000).map(x => x.toString -> x).toMap
val sum = xs.values.sum
val sumsq = xs.values.map(x => x*x).sum
Если вы делаете это время от времени, это не имеет большого значения.Но если вы обратите внимание на то, что происходит, для каждого элемента списка вы сначала воссоздаете его (значения), затем суммируете его (в рамке), затем воссоздаете его снова (значения), а затем воссоздаете его еще раз в квадрате с боксом (карта)затем подведите итог.Это как минимум шесть созданий объекта и пять полных обходов, чтобы сделать два добавления и одно умножение на элемент. Невероятно неэффективно.
Вы можете попытаться добиться большего успеха, избегая многократной рекурсии и проходя по карте только один раз, используя складку:
val (sum,sumsq) = ((0,0) /: xs){ case ((sum,sumsq),(_,v)) => (sum + v, sumsq + v*v) }
И этонамного лучше, с примерно в 15 раз лучшей производительностью на моей машине.Но у вас все еще есть три создания объекта на каждую итерацию.Если вместо этого вы
case class SSq(var sum: Int = 0, var sumsq: Int = 0) {
def +=(i: Int) { sum += i; sumsq += i*i }
}
val ssq = SSq()
xs.foreach(x => ssq += x._2)
вы снова в два раза быстрее, потому что вы сократили бокс.Если у вас есть данные в массиве и используется цикл while, то вы можете избежать создания и упаковки всех объектов и ускорить их еще в 20 раз.
Теперь, как говорится, вы могли бы также выбрал рекурсивную функцию для вашего массива:
val ar = Array.range(0,10000)
def suma(xs: Array[Int], start: Int = 0, sum: Int = 0, sumsq: Int = 0): (Int,Int) = {
if (start >= xs.length) (sum, sumsq)
else suma(xs, start+1, sum+xs(start), sumsq + xs(start)*xs(start))
}
и записан таким образом, это так же быстро, как изменяемый SSq.Но если мы вместо этого сделаем это:
def sumb(xs: Array[Int], start: Int = 0, ssq: (Int,Int) = (0,0)): (Int,Int) = {
if (start >= xs.length) ssq
else sumb(xs, start+1, (ssq._1+xs(start), ssq._2 + xs(start)*xs(start)))
}
мы теперь снова в 10 раз медленнее, потому что мы должны создавать объект на каждом шаге.
Итак, суть в том, что это на самом деле имеет значение только в том случае, если у вас есть неизменность, когда вы не можете удобно переносить свою структуру обновления как независимые аргументы в метод.Как только вы выйдете за пределы сложности, где это работает, изменчивость может стать большим выигрышем.
Создание кумулятивного объекта
Если вам нужно создать сложный объект с полями n
из потенциально ошибочных данныхВы можете использовать шаблон построителя, который выглядит следующим образом:
abstract class Built {
def x: Int
def y: String
def z: Boolean
}
private class Building extends Built {
var x: Int = _
var y: String = _
var z: Boolean = _
}
def buildFromWhatever: Option[Built] = {
val b = new Building
b.x = something
if (thereIsAProblem) return None
b.y = somethingElse
// check
...
Some(b)
}
Этот только работает с изменяемыми данными.Конечно, есть и другие варианты:
class Built(val x: Int = 0, val y: String = "", val z: Boolean = false) {}
def buildFromWhatever: Option[Built] = {
val b0 = new Built
val b1 = b0.copy(x = something)
if (thereIsAProblem) return None
...
Some(b)
}
, который во многих отношениях даже чище, за исключением того, что вы должны копировать свой объект один раз для каждого внесенного вами изменения, что может быть мучительно медленным.И ни один из них не является особенно пуленепробиваемым;для этого вы, вероятно, захотите
class Built(val x: Int, val y: String, val z: Boolean) {}
class Building(
val x: Option[Int] = None, val y: Option[String] = None, val z: Option[Boolean] = None
) {
def build: Option[Built] = for (x0 <- x; y0 <- y; z0 <- z) yield new Built(x,y,z)
}
def buildFromWhatever: Option[Build] = {
val b0 = new Building
val b1 = b0.copy(x = somethingIfNotProblem)
...
bN.build
}
, но опять же, есть много накладных расходов.