F #: dejar mutable vs.ref

82

Primero, reconozco la posibilidad de que esta pregunta sea un duplicado; sólo házmelo saber.

Tengo curiosidad por saber cuál es la "mejor práctica" general para aquellas situaciones en las que se desea la mutabilidad. F # parece ofrecer dos facilidades para esto: el let mutableenlace, que parece funcionar como variables en "la mayoría" de los lenguajes, y la celda de referencia (creada con la reffunción) que requiere una desreferenciación explícita para su uso.

Hay un par de casos en los que uno es "forzado" en uno u otro: la interoperabilidad de .NET tiende a usarse mutable con <-, y en los cálculos de flujo de trabajo uno debe usar refcon :=. Entonces, esos casos son bastante claros, pero tengo curiosidad por saber qué hacer al crear mis propias variables mutables fuera de esos escenarios. ¿Qué ventaja tiene un estilo sobre el otro? (Quizás sería útil conocer mejor la implementación).

¡Gracias!

J Cooper
fuente
4
Tenga en cuenta que en F # versión 4, mutable se puede usar donde solía necesitar una ref. blogs.msdn.com/b/fsharpteam/archive/2014/11/12/…
James Moore

Respuestas:

133

Solo puedo apoyar lo que dijo Gradbot : cuando necesito una mutación, lo prefiero let mutable.

Con respecto a la implementación y las diferencias entre las dos refceldas, esencialmente se implementan mediante un registro muy simple que contiene un campo de registro mutable. Podrías escribirlos fácilmente tú mismo:

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 }

Una diferencia notable entre los dos enfoques es que let mutablealmacena el valor mutable en la pila (como una variable mutable en C #) mientras refalmacena el valor mutable en un campo de un registro asignado al montón. Esto puede tener algún impacto en el rendimiento, pero no tengo números ...

Gracias a esto, los valores mutables que se usan refpueden tener un alias, lo que significa que puede crear dos valores que hagan referencia al mismo valor mutable:

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!
Tomas Petricek
fuente
2
Solo un recordatorio: estar en la pila o en el montón es un detalle de implementación y no es exactamente relevante para la pregunta (pero, no obstante, una excelente respuesta)
Bruno Brant
5
Yo diría que saber si algo incurre en la sobrecarga de la asignación y recopilación del montón o no es extremadamente relevante a la hora de decidir cuál es una mejor práctica.
jackmott
@jackmott echa un vistazo a este artículo de Eric Lippert llamado The Stack Is An Implementation Detail
jaromey
5
@jaromey sí, entiendo que fundamentalmente no estoy de acuerdo completamente con Eric en ese punto. No puede dejar de lado las consideraciones de rendimiento al considerar lo que es una "mejor práctica". A menudo se afirma que el rendimiento aún no importa, por lo que gran parte del software es lento debido a la muerte de mil detalles de implementación.
jackmott
18

Pregunta relacionada: "Mencionaste que los valores mutables locales no pueden ser capturados por un cierre, por lo que debes usar ref en su lugar. La razón de esto es que los valores mutables capturados en el cierre deben asignarse en el montón (porque el cierre se asigna en el montón) ". de F # ref-mutable vars vs campos de objeto

Creo que let mutablese prefiere a las celdas de referencia. Personalmente, solo uso celdas de referencia cuando son necesarias.

La mayoría del código que escribo no usa variables mutables gracias a la recursividad y las llamadas de cola. Si tengo un grupo de datos mutables, uso un registro. Para los objetos que uso let mutablepara hacer variables privadas mutables. Realmente solo uso celdas de referencia para cierres, generalmente eventos.

gradbot
fuente
9

Como se describe en este artículo del blog de MSDN en la sección Uso simplificado de valores mutables , ya no necesita celdas de referencia para lambdas. Entonces, en general, ya no los necesita en absoluto.

Andrii
fuente
4

Este artículo de Brian podría proporcionar una respuesta.

Los mutables son fáciles de usar y eficientes (sin envoltura), pero no se pueden capturar en lambdas. Las celdas de referencia se pueden capturar, pero son detalladas y menos eficientes (? - no estoy seguro de esto).

Mau
fuente
"(...) y menos eficiente" - probablemente, un tipo de contenedor requiere más memoria.
JMCF125
2
Esto ha cambiado, ya que F # 4.0 mutable se puede capturar en la mayoría de los casos y la necesidad de ref es ahora mucho menor.
Abel
3

Es posible que desee echar un vistazo a la sección Datos mutables en el wikilibro.

Para mayor comodidad, aquí hay algunas citas relevantes:

La palabra clave mutable se usa con frecuencia con tipos de registros para crear registros mutables.

Las variables mutables son algo limitadas: las mutables son inaccesibles fuera del alcance de la función donde están definidas. Específicamente, esto significa que no es posible hacer referencia a un mutable en una subfunción de otra función.

Las celdas de referencia superan algunas de las limitaciones de las mutables. De hecho, las celdas de referencia son tipos de datos muy simples que envuelven un campo mutable en un tipo de registro.

Dado que las celdas de referencia se asignan en el montón, se pueden compartir entre múltiples funciones

danlei
fuente