Когда императивный стиль подходит лучше? - PullRequest
15 голосов
/ 01 февраля 2012

Из Программирование в Scala (второе издание) , нижняя часть стр.98:

Сбалансированное отношение для программистов Scala

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

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

Но второе предложение: «Используйте переменные, изменяемые объекты и методы с побочными эффектами, когда у вас есть особая потребность и обоснование для них».не так хорошо объяснено.

Итак, мой вопрос:

В чем обоснованность или особая необходимость использования переменных, изменяемых объектов и методов с побочным эффектом?


Ps: Было бы здорово, если бы кто-то мог привести несколько примеров для каждого из них (помимо объяснения).

Ответы [ 4 ]

16 голосов
/ 01 февраля 2012

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

В настоящее время (Scala 2.9.1) хорошим примером является суммирование диапазонов:

(1 to 1000000).foldLeft(0)(_ + _)

Versus:

var x = 1
var sum = 0
while (x <= 1000000) {
  sum += x
  x += 1
}

Если вы профилируете их, вы заметите существенную разницу в скорости выполнения. Поэтому иногда производительность - действительно хорошее оправдание.

8 голосов
/ 01 февраля 2012

Простота незначительных обновлений

Одной из причин использования изменчивости является отслеживание какого-либо текущего процесса.Например, предположим, что я редактирую большой документ и у меня есть сложный набор классов для отслеживания различных элементов текста, истории редактирования, положения курсора и так далее.Теперь предположим, что пользователь нажимает на другую часть текста.Нужно ли заново создавать объект документа, копируя много полей, но не поле 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
}

, но опять же, есть много накладных расходов.

5 голосов
/ 01 февраля 2012

Я обнаружил, что императивный / изменчивый стиль лучше подходит для алгоритмов динамического программирования.Если вы настаиваете на неизменности, для большинства людей программирование сложнее, и в итоге вы используете огромное количество памяти и / или переполняете стек.Один пример: Динамическое программирование в функциональной парадигме

3 голосов
/ 01 февраля 2012

Некоторые примеры:

  1. (Первоначально комментарий) Любая программа должна выполнять некоторый ввод и вывод (иначе, это бесполезно).Но по определению ввод / вывод является побочным эффектом и не может быть осуществлен без вызова методов с побочными эффектами.

  2. Одним из основных преимуществ Scala является возможность использования библиотек Java.Многие из них полагаются на изменяемые объекты и методы с побочными эффектами.

  3. Иногда вам требуется var из-за области видимости.См. Temperature4 в этом блоге для примера.

  4. Параллельное программирование.Если вы используете актеров, отправка и получение сообщений являются побочным эффектом;если вы используете потоки, синхронизация с блокировками является побочным эффектом, а блокировки являются изменяемыми;управляемый событиями параллелизм - все о побочных эффектах;фьючерсы, параллельные коллекции и т. д. являются изменчивыми.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...