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

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

Пример был бы отличным, например. добавление 1 к числу, желательно в Idris, но Scala тоже подойдет

p.s. есть тег для мутации? не вижу ни одного


person joel    schedule 14.05.2021    source источник
comment
Что ж, в Scala у вас есть var, которые вы можете изменить в любой момент; но это не будет чисто функционально. Однако, если переменная является внутренней для какого-либо метода и ее изменение невидимо для внешних пользователей, мы по-прежнему называем этот метод ссылочно-прозрачным, и, таким образом, изменение является просто внутренней (и, возможно, прагматичной) деталью реализации.   -  person Luis Miguel Mejía Suárez    schedule 14.05.2021
comment
@LuisMiguelMejíaSuárez да, я знаком с var и локальной мутацией, но на самом деле я надеюсь написать это в Идрисе, где нет var, следовательно, чисто функционально. Я спрашиваю и в Scala, потому что, если это возможно в Idris, вероятно, это возможно и в Scala.   -  person joel    schedule 14.05.2021
comment
Мутация = перерыв RT = не чисто функциональный, и точка. Тем не менее, мы занимаемся функциональным программированием не для того, чтобы хвастаться, а потому, что это помогает вам поддерживать наши программы, и, таким образом, прагматичные функциональные программисты понимают, что иногда включенная изменчивость не только в порядке, но и лучше (для производительности или удобства чтения) , однако это не означает, что он чисто функционален, это не имеет смысла. Таким образом, ваш вопрос должен быть переформулирован как как выполнить мутацию в Idris и удалить scala для уравнения; теперь, возможно, вы захотите взглянуть на Ref, как вы могли бы поделиться некоторым изменяемым состоянием.   -  person Luis Miguel Mejía Suárez    schedule 14.05.2021
comment
@LuisMiguelMejíaSuárez Я не уверен, что мы на одной волне. Я думаю о том, как IO возвращает действие для выполнения, и когда вы его выполняете, вы получаете результирующее значение, к которому затем можно получить доступ через map и т. д. Точно так же, возможно ли иметь тип, который означает, что это будет производить новое значение из текущего при выполнении, о чем мы можем рассуждать сейчас через map и т. д. Я ожидаю, что это будет похоже на монаду состояния, но использовать только один адрес памяти   -  person joel    schedule 14.05.2021
comment
@LuisMiguelMejíaSuárez Ref выглядит многообещающе. Вы имеете в виду от кошек?   -  person joel    schedule 14.05.2021
comment
Ну, вашим действием для выполнения может быть модификация переменной val Inc = IO { i += 1 }, вы также можете взглянуть на Ref, которая вместо того, чтобы позволить вам изменять что-то само по себе, а скорее позволяет вам поделиться некоторыми ( mutable) в нескольких (параллельных) операциях, хотя на самом деле это не дает вам доступа для изменения значения, но дает вам возможность создать новое состояние, учитывая предыдущее и внутренне он может cats-effect да) изменять один и тот же адрес памяти.   -  person Luis Miguel Mejía Suárez    schedule 14.05.2021
comment
Также не исключено, что компилятор/среда выполнения в конечном итоге интерпретируют мутацию, описанную чисто функциональным способом, как мутацию на месте, даже если код не написан таким образом. Вы написали чисто FP-код (например, монаду состояния) и обнаружили, что его производительность недостаточна, или это просто случай, потому что наивная реализация выглядит медленно, я думаю, что она медленная?   -  person Levi Ramsey    schedule 14.05.2021
comment
@LeviRamsey Я еще не написал код, но на самом деле это оболочка для кода C++, которая выполняет мутацию на месте.   -  person joel    schedule 14.05.2021
comment
Крайне важно, чтобы API мог гарантировать мутацию на месте, потому что объект будет потенциально очень большим массивом, до ГБ данных, а производительность очень важна.   -  person joel    schedule 14.05.2021
comment
@joel, в этом случае вам нужен только IO, который описывает мутацию на месте, как в моем примере.   -  person Luis Miguel Mejía Suárez    schedule 14.05.2021
comment
Если def foo() = x+1 и x изменяемы и видны за пределами foo, то foo не является прозрачным с точки зрения ссылок. Следовательно, мутация чисто функциональным путем бессмысленна. Можно ли поджарить кусок льда, чтобы он не растаял?   -  person Dima    schedule 14.05.2021
comment
За последние несколько лет я неоднократно указывал на эта глава в Красной книге, которая содержит раздел Чисто функциональное изменяемое состояние. Также где-то летала реализация ST-монады. Я знаю, что это существует, но я никогда не использовал его ни для чего.   -  person Andrey Tyukin    schedule 15.05.2021
comment
@AndreyTyukin ST не имеет смысла в Scala, ST в основном просто var   -  person Luis Miguel Mejía Suárez    schedule 15.05.2021
comment
@LuisMiguelMejíaSuárez Дело не в var. Речь идет о доказательстве того, что ссылки на этот var не ускользают.   -  person Andrey Tyukin    schedule 15.05.2021
comment
@AndreyTyukin, как и во всем чистом ФП в Scala, нужен ученик. Ничто не мешает вам делать def thisIsNotFP(): IO[Unit] = { executeSideEffects(); IO.unit }   -  person Luis Miguel Mejía Suárez    schedule 15.05.2021
comment
@LuisMiguelMejíaSuárez Наличие доказательства и обеспечение его соблюдения компилятором - две ортогональные проблемы. Конечно можно println("whatever, wherever"), не в этом дело.   -  person Andrey Tyukin    schedule 15.05.2021


Ответы (1)


Нет, в Scala это невозможно.

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

def update(arr: Array[Int], idx: Int, value: Int): Array[Int] =
  arr.take(idx) ++ Array(value) ++ arr.drop(idx + 1)

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

def update(arr: Array[Int], idx: Int, value: Int): Array[Int] = {
  arr(idx) = value
  arr
}

Следующий код будет нормально работать с первой реализацией, но сломается со второй:

val arr = Array(1, 2, 3)
assert(arr(1) == 2)
val arr2 = update(arr, 1, 42)
assert(arr2(1) == 42) // so far, so good…
assert(arr(1) == 2) // oh noes!

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

Я знаю два языка, в которых есть эта функция: ATS и Haskell. Если вам нужны подробности, я бы порекомендовал этот доклад Саймона Пейтона-Джонса, где он объясняет реализацию в Haskell:

https://youtu.be/t0mhvd3-60Y

С тех пор поддержка линейных типов была объединена с GHC: https://www.tweag.io/blog/2020-06-19-linear-types-merged/

person Matthias Berndt    schedule 14.05.2021
comment
это очень напоминает мне, как собственность в rust может использоваться для предоставления чисто функционального API с мутациями. Возможно, это пример линейных типов - person joel; 15.05.2021
comment
и Idris 2, кажется, поддерживает его через количественные типы . Дает ли Идрис гарантии памяти, мне еще предстоит узнать - person joel; 15.05.2021
comment
Разве вам не нужны аффинные типы для этого? Если вы ни для чего не используете arr2, все должно быть в порядке. - person Cactus; 16.05.2021
comment
@Cactus Я не встречал аффинных типов. Где я могу узнать больше? Их нет ни в книге (по крайней мере, в указателе), ни на веб-сайте документации. - person joel; 17.05.2021
comment
@ Кактус ах, нашел, что означает аффинное, хотя я не уверен, что понимаю практическую разницу. Зачем мне вызывать функцию, если я не собираюсь использовать ее возвращаемое значение? - person joel; 17.05.2021