Подтвердить что ты не робот

Почему неизменяемые объекты позволяют выполнять функциональное программирование?

Я пытаюсь изучить scala, и я не могу понять эту концепцию. Почему сделать объект неизменным помогает предотвратить побочные эффекты в функциях. Может кто-нибудь объяснить, как мне пять?

4b9b3361

Ответ 1

Интересный вопрос, немного сложно ответить.

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

Существует множество моделей вычислений, которые предоставляют такие формализмы, как lambda calculus и turing machines. И там определенная степень эквивалентности между ними (см. этот вопрос, для обсуждения).

В очень реальном смысле программы с изменчивостью и некоторыми другими побочными эффектами имеют прямое отображение в функциональную программу. Рассмотрим этот пример:

a = 0
b = 1
a = a + b

Вот два способа сопоставления его с функциональной программой. Первый, a и b являются частью "состояния", и каждая строка является функцией из состояния в новое состояние:

state1 = (a = 0, b = ?)
state2 = (a = state1.a, b = 1)
state3 = (a = state2.a + state2.b, b = state2.b)

Здесь другой, где каждая переменная связана с временем:

(a, t0) = 0
(b, t1) = 1
(a, t2) = (a, t0) + (b, t1)

Итак, учитывая вышеизложенное, почему бы не использовать изменчивость?

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

Как следствие, очень мало прогресса в отношении концепций программирования с изменчивостью. Знаменитые образцы дизайна не были получены через исследование, и у них нет какой-либо математической поддержки. Вместо этого они являются результатом лет и лет испытаний и ошибок, и некоторые из них с тех пор оказались ошибочными. Кто знает о других десятках "шаблонов дизайна", которые встречаются повсюду?

Между тем, программисты Haskell придумали Functors, Monads, Co-monads, Zippers, Applicatives, Lenses... десятки концепций с математической поддержкой и, самое главное, фактические шаблоны того, как код составлен для составления программ. Вещи, которые вы можете использовать, чтобы рассуждать о своей программе, увеличивать повторное использование и улучшать правильность. Взгляните на Typeclassopedia для примера.

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

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

Ответ 2

Короче говоря, если функция мутирует объект, то она имеет побочные эффекты. Мутация является побочным эффектом. Это справедливо по определению.

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

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

Теперь я скажу кучу других вещей в надежде, что это поможет вам понять.

Основой концепции переменной в функциональных языках является ссылочная прозрачность.

Ссылочная прозрачность означает, что нет никакой разницы между значением и ссылкой на это значение. На языке, где это правда, гораздо проще подумать о работе программы, так как вам никогда не нужно останавливаться и спрашивать, это значение или ссылка на значение? Любой, кто когда-либо программировал в C, признает, что большая часть проблемы изучения этой парадигмы - это знание того, что есть во все времена.

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

(Предупреждение, я собираюсь сделать аналогию.)

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

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

Вы рассчитывали на номер телефона, соответствующий этому человеку, но на самом деле это не так. В системе с номерами телефонов отсутствует ссылочная прозрачность: число не всегда ВСЕГДА такое же, как у человека.

Функциональные языки избегают этой проблемы. Вы можете указать свой номер телефона, и люди всегда смогут связаться с вами до конца своей жизни и никогда не смогут связаться ни с кем другим в этом номере.

Однако на платформе Java все может измениться. То, что, по вашему мнению, было одним делом, через минуту может превратиться в другое. Если это так, как вы можете остановить его?

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

Практически говоря, преимущества кодирования с неизменяемыми типами:

  • Ваш код проще читать, когда читателю не нужно искать неожиданные побочные эффекты.
  • Если вы используете несколько потоков, вам не нужно беспокоиться о блокировке, потому что общие объекты никогда не могут измениться. Когда у вас есть побочные эффекты, вам нужно действительно продумать код и выяснить все места, где два потока могут попытаться изменить один и тот же объект одновременно и защитить от проблем, которые это может вызвать.
  • Теоретически, по крайней мере, компилятор может лучше оптимизировать код, если он использует только неизменные типы. Я не знаю, может ли Java сделать это эффективно, хотя, поскольку это позволяет побочные эффекты. Во всяком случае, это тотализатор, потому что есть некоторые проблемы, которые могут быть решены гораздо эффективнее с помощью побочных эффектов.

Ответ 3

Я работаю с этим 5-летним объяснением:

class Account(var myMoney:List[Int] = List(10, 10, 1, 1, 1, 5)) {
  def getBalance = println(myMoney.sum + " dollars available")
  def myMoneyWithInterest = {
    myMoney = myMoney.map(_ * 2)
    println(myMoney.sum + " dollars will accru in 1 year")
  }
}

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

Вы делаете следующее:

scala> val myAccount = new Account()
myAccount: Account = [email protected]

scala> myAccount.getBalance
28 dollars available

scala> myAccount.myMoneyWithInterest
56 dollars will accru in 1 year

scala> myAccount.getBalance
56 dollars available

We mutated баланс счета, когда мы хотели проверить наш текущий баланс плюс интерес к лету. Теперь у нас неправильный баланс аккаунта. Плохая новость для банка!

Если мы использовали val вместо var для отслеживания myMoney в определении класса, мы бы не смогли mutate доллары и поднять наш баланс.

При определении класса (в REPL) с помощью val:

error: reassignment to val
             myMoney = myMoney.map(_ * 2

Scala сообщает нам, что мы хотели значение immutable, но пытаемся его изменить!

Благодаря Scala, мы можем переключиться на val, переписать наш метод myMoneyWithInterest и быть уверенным, что наш класс Account никогда не изменит баланс.

Ответ 4

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

Теперь представьте функцию, возвращающую атрибут content некоторого объекта. Если этот content может изменить, функция может возвращать разные результаты для разных вызовов с тем же аргументом. = > нет более функционального программирования.

Ответ 5

Сначала несколько определений:

  • Побочным эффектом является изменение состояния - также называемое мутацией.
  • Неизменяемый объект - это объект, который не поддерживает мутацию (побочные эффекты).

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

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

Ответ 6

Ответ Nate велик, и вот какой-то пример.

В функциональном программировании существует важная функция, когда вы вызываете функцию с тем же аргументом, вы всегда получаете одинаковое возвращаемое значение.

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

class MyValue(val value: Int)

def plus(x: MyValue) = x.value + 10

val x = new MyValue(10)
val y = plus(x) // y is 20
val z = plus(x) // z is still 20, plus(x) will always yield 20

Но если у вас есть изменяемые объекты, вы не можете гарантировать, что плюс (x) всегда будет возвращать одно и то же значение для одного экземпляра MyValue.

class MyValue(var value: Int)

def plus(x: MyValue) = x.value + 10

val x = new MyValue(10)
val y = plus(x) // y is 20
x.value = 30
val z = plus(x) // z is 40, you can't for sure what value will plus(x) return because MyValue.value may be changed at any point.

Ответ 7

Почему неизменяемые объекты допускают функциональное программирование?

Они не делают.

Возьмем одно определение "функция" или "prodecure", "рутина" или "метод", которое, как я полагаю, относится ко многим языкам программирования: "Раздел кода, обычно называемый, принимающий аргументы и/или возвращающий значение."

Возьмем одно определение "функциональное программирование:" "Программирование с использованием функций". Возможность программирования с функциями не зависит от изменения состояния.

Например, Scheme считается функциональным языком программирования. Он включает в себя хвостовые звонки, функции более высокого порядка и агрегированные операции с использованием функций. Он также имеет изменяемые объекты. Хотя изменчивость разрушает некоторые хорошие математические качества, это не обязательно препятствует "функциональному программированию".