F #: let mutable vs. ref

83

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

Мне любопытно, какова общая «лучшая практика» для тех ситуаций, когда желательна изменчивость. Кажется, что F # предлагает для этого две возможности: let mutableпривязку, которая работает как переменные в «большинстве» языков, и ссылочная ячейка (созданная с помощью refфункции), для использования которой требуется явное разыменование.

Есть пара случаев, когда одно «принудительно» совпадает с тем или другим: взаимодействие .NET имеет тенденцию использовать изменяемый с <-и в вычислениях рабочего процесса, которые необходимо использовать refс :=. Так что эти случаи довольно ясны, но мне любопытно, что делать при создании моих собственных изменяемых переменных вне этих сценариев. Какое преимущество у одного стиля перед другим? (Возможно, поможет более глубокое понимание реализации.)

Благодаря!

Дж. Купер
источник
4
Обратите внимание, что в F # версии 4, mutable можно использовать там, где вам раньше требовалась ссылка. blogs.msdn.com/b/fsharpteam/archive/2014/11/12/…
Джеймс Мур,

Ответы:

134

Я могу только поддержать то, что сказал градбот - я предпочитаю, когда мне нужна мутация let mutable.

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

type ref<'T> =  // '
  { mutable value : 'T } // '

// the ref function, ! and := operators look like this:
let (!) (a:ref<_>) = a.value
let (:=) (a:ref<_>) v = a.value <- v
let ref v = { value = v }

Заметное различие между этими двумя подходами состоит в том, let mutableчто изменяемое значение сохраняется в стеке (как изменяемая переменная в C #), а refизменяемое значение сохраняется в поле записи, выделенной кучей. Это может повлиять на производительность, но цифр у меня нет ...

Благодаря этому, используемые изменяемые значения refмогут иметь псевдонимы - это означает, что вы можете создать два значения, которые ссылаются на одно и то же изменяемое значение:

let a = ref 5  // allocates a new record on the heap
let b = a      // b references the same record
b := 10        // modifies the value of 'a' as well!

let mutable a = 5 // mutable value on the stack
let mutable b = a // new mutable value initialized to current value of 'a'
b <- 10           // modifies the value of 'b' only!
Томаш Петричек
источник
2
Просто напоминание: нахождение в стеке или куче - это деталь реализации, которая не совсем относится к вопросу (но тем не менее отличный ответ)
Бруно Брант
5
Я бы сказал, что знание того, вызывает ли что-либо накладные расходы на выделение и сбор кучи, чрезвычайно важно при принятии решения о том, что является лучшей практикой.
Джекмотт 02
@jackmott ознакомьтесь с этой статьей Эрика Липперта под названием «Стек - это деталь реализации»
jaromey
5
@jaromey Да, я понимаю, что я принципиально не согласен с Эриком в этом вопросе. Вы не можете оставить в стороне соображения производительности при рассмотрении того, что является «лучшей практикой». Часто утверждают, что производительность не имеет значения, но многие программы работают медленно из-за тысяч деталей реализации.
jackmott
18

Связанный вопрос: «Вы упомянули, что локальные изменяемые значения не могут быть захвачены замыканием, поэтому вместо этого вам нужно использовать ref. Причина этого в том, что изменяемые значения, захваченные в замыкании, должны быть размещены в куче (поскольку закрытие выделяется на куча) ". из F # ref-mutable vars против полей объекта

я думаю let mutable предпочтительнее ссылочных ячеек. Я лично использую ссылочные ячейки только тогда, когда они требуются.

Большая часть кода, который я пишу, не использует изменяемые переменные благодаря рекурсии и хвостовым вызовам. Если у меня есть группа изменяемых данных, я использую запись. Для объектов, которые я использую let mutableдля создания частных изменяемых переменных. Я действительно использую ссылочные ячейки только для замыканий, как правило, событий.

Gradbot
источник
9

Как описано в этой статье блога MSDN в разделе « Упрощенное использование изменяемых значений» , вам больше не нужны ячейки ссылки для лямбда-выражений. Так что в общем они вам вообще больше не нужны.

Андрей
источник
4

Эта статья Брайана может дать ответ.

Изменяемые элементы просты в использовании и эффективны (без упаковки), но не могут быть записаны в лямбдах. Референсные ячейки могут быть захвачены, но они подробны и менее эффективны (? - не уверен в этом).

Мау
источник
«(...) и менее эффективный» - вероятно, тип-оболочка требует больше памяти.
JMCF125 02
2
Это изменилось, поскольку изменяемый F # 4.0 может быть записан в большинстве случаев, и необходимость в ref теперь намного меньше.
Abel
3

Возможно, вы захотите взглянуть на раздел «Изменяемые данные » в викиучебнике.

Для удобства приведем несколько уместных цитат:

Ключевое слово mutable часто используется с типами записей для создания изменяемых записей.

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

Ячейки Ref позволяют обойти некоторые ограничения изменяемых. Фактически, ячейки ref - это очень простой тип данных, который включает изменяемое поле в тип записи.

Поскольку ячейки ref выделяются в куче, они могут использоваться несколькими функциями.

данлей
источник