Celdas de referencia (F#)
Las celdas de referencia son ubicaciones de almacenamiento que permiten crear valores mutables con semántica de referencias.
ref expression
Comentarios
Se utiliza el operador ref antes de un valor para crear una nueva celda de referencia que encapsula el valor. A continuación, se puede cambiar el valor subyacente, porque es mutable.
Una celda de referencia contiene un valor real; no una mera dirección. Al crear una celda de referencia mediante el operador ref, se crea una copia del valor subyacente como valor mutable encapsulado.
Se puede desreferenciar una celda de referencia mediante el operador ! (bang).
En el ejemplo de código siguiente se muestran la declaración y el uso de celdas de referencia.
// Declare a reference.
let refVar = ref 6
// Change the value referred to by the reference.
refVar := 50
// Dereference by using the ! operator.
printfn "%d" !refVar
El resultado es 50
Las celdas de referencia son instancias del tipo de registro genérico Ref, que se declara como sigue.
type Ref<'a> =
{ mutable contents: 'a }
El tipo 'a ref es un sinónimo de Ref<'a>. Tanto el compilador como IntelliSense en el IDE, muestran el primero para este tipo, pero la definición subyacente es el segundo.
El operador ref crea una nueva celda de referencia. El código siguiente es la declaración del operador ref.
let ref x = { contents = x }
En la tabla siguiente se muestran las características que están disponibles en la celda de referencia.
Operador, miembro o campo |
Descripción |
Tipo |
Definición |
---|---|---|---|
! (operador de desreferencia) |
Devuelve el valor subyacente. |
'a ref -> 'a |
let (!) r = r.contents |
:= (operador de asignación) |
Cambia el valor subyacente. |
'a ref -> 'a -> unit |
let (:=) r x = r.contents <- x |
ref (operador) |
Encapsula un valor en una nueva celda de referencia. |
'a -> 'a ref |
let ref x = { contents = x } |
Value (propiedad) |
Obtiene o establece el valor subyacente. |
unit -> 'a |
member x.Value = x.contents |
contents (campo de registro) |
Obtiene o establece el valor subyacente. |
'a |
let ref x = { contents = x } |
Hay varias maneras de tener acceso al valor subyacente. El valor devuelto por el operador de desreferencia (!) no es un valor asignable. Por consiguiente, si se va a modificar el valor subyacente, se debe utilizar el operador de asignación (:=) en su lugar.
Tanto la propiedad Value como el campo contents son valores asignables. Así pues, se pueden utilizar para obtener acceso al valor subyacente o cambiarlo, como se muestra en el código siguiente.
let xRef : int ref = ref 10
printfn "%d" (xRef.Value)
printfn "%d" (xRef.contents)
xRef.Value <- 11
printfn "%d" (xRef.Value)
xRef.contents <- 12
printfn "%d" (xRef.contents)
La salida es la siguiente.
10
10
11
12
El campo contents se proporciona por motivos de compatibilidad con otras versiones de ML y generará una advertencia durante la compilación. Para deshabilitar la advertencia, utilice la opción --mlcompatibility del compilador. Para obtener más información, vea Opciones del compilador (F#).
Ejemplo
El código siguiente muestra el uso de celdas de referencia al pasar parámetros. El tipo Incrementor tiene un método Increment que toma un parámetro que incluye byref en el tipo de parámetro. byref en el tipo de parámetro indica que los llamadores deben pasar una celda de referencia o bien la dirección de una variable típica del tipo especificado, en este caso, int. En el código restante se muestra cómo llamar a Increment con estos dos tipos de argumentos y también el uso del operador ref en una variable para crear una celda de referencia (ref myDelta1). A continuación, se muestra el uso del operador de dirección de (&) para generar un argumento adecuado. Por último, se vuelve a llamar al método Increment con una celda de referencia que se declara mediante un enlace let. En la última línea del código se muestra el uso del operador ! a fin de desreferenciar la celda de referencia para su impresión.
type Incrementor(delta) =
member this.Increment(i : int byref) =
i <- i + delta
let incrementor = new Incrementor(1)
let mutable myDelta1 = 10
incrementor.Increment(ref myDelta1)
// Prints 10:
printfn "%d" myDelta1
let mutable myDelta2 = 10
incrementor.Increment(&myDelta2)
// Prints 11:
printfn "%d" myDelta2
let refInt = ref 10
incrementor.Increment(refInt)
// Prints 11:
printfn "%d" !refInt
Para obtener más información sobre cómo pasar por referencia, vea Parámetros y argumentos (F#).
Nota
Los programadores de C# deben saber que ref funciona de forma distinta en F# que en C#.Por ejemplo, el uso de ref al pasar un argumento no tiene el mismo efecto en F# que en C#.
Celdas de referencia frente a variables mutables
A menudo, se pueden usar celdas de referencia y variables mutables en las mismas situaciones. Sin embargo, existen algunas situaciones en las que no se pueden utilizar variables mutables y es preciso usar una celda de referencia en su lugar. En general, es preferible utilizar variables mutables si el compilador las acepta. Sin embargo, en las expresiones que generan cierres, el compilador notificará que no puede utilizar variables mutables. Los cierres son funciones locales generadas por determinadas expresiones de F#, tales como las expresiones lambda, las expresiones de secuencia, las expresiones de cálculo y las funciones currificadas que utilizan argumentos aplicados parcialmente. Los cierres generados por estas expresiones se almacenan para evaluarlos posteriormente. Este proceso no es compatible con las variables mutables. Por consiguiente, si se necesita el estado mutable en una expresión de este tipo, hay que utilizar celdas de referencia. Para obtener más información acerca de los cierres, vea Cierres (F#).
En el ejemplo de código siguiente se muestra el escenario en que se debe utilizar una celda de referencia.
// Print all the lines read in from the console.
let PrintLines1() =
let mutable finished = false
while not finished do
match System.Console.ReadLine() with
| null -> finished <- true
| s -> printfn "line is: %s" s
// Attempt to wrap the printing loop into a
// sequence expression to delay the computation.
let PrintLines2() =
seq {
let mutable finished = false
// Compiler error:
while not finished do
match System.Console.ReadLine() with
| null -> finished <- true
| s -> yield s
}
// You must use a reference cell instead.
let PrintLines3() =
seq {
let finished = ref false
while not !finished do
match System.Console.ReadLine() with
| null -> finished := true
| s -> yield s
}
En el código anterior, la celda de referencia finished se incluye en el estado local, es decir, las variables que pertenecen al cierre se crean y usan completamente dentro de la expresión (en este caso, una expresión de secuencia). Imagine lo que sucede cuando las variables no son locales. Los cierres también pueden tener acceso al estado no local, pero cuando esto ocurre, las variables se copian y almacenan por valor. Este proceso se denomina semántica de valores. Esto significa que los valores quedan almacenados en el momento de la copia, de modo que no se refleja ningún cambio subsiguiente de las variables. Si se desea realizar el seguimiento de los cambios de variables no locales o, en otras palabras, si se necesita un cierre que interactúe con el estado no local mediante la semántica de referencias, se debe utilizar una celda de referencia.
En los ejemplos de código siguientes se muestra el uso de las celdas de referencia en los cierres. En este caso, el cierre es el resultado de la aplicación parcial de argumentos de función.
// The following code demonstrates the use of reference
// cells to enable partially applied arguments to be changed
// by later code.
let increment1 delta number = number + delta
let mutable myMutableIncrement = 10
// Closures created by partial application and literals.
let incrementBy1 = increment1 1
let incrementBy2 = increment1 2
// Partial application of one argument from a mutable variable.
let incrementMutable = increment1 myMutableIncrement
myMutableIncrement <- 12
// This line prints 110.
printfn "%d" (incrementMutable 100)
let myRefIncrement = ref 10
// Partial application of one argument, dereferenced
// from a reference cell.
let incrementRef = increment1 !myRefIncrement
myRefIncrement := 12
// This line also prints 110.
printfn "%d" (incrementRef 100)
// Reset the value of the reference cell.
myRefIncrement := 10
// New increment function takes a reference cell.
let increment2 delta number = number + !delta
// Partial application of one argument, passing a reference cell
// without dereferencing first.
let incrementRef2 = increment2 myRefIncrement
myRefIncrement := 12
// This line prints 112.
printfn "%d" (incrementRef2 100)
Vea también
Referencia
Referencia de símbolos y operadores (F#)