Byrefs
F# tiene dos áreas de características principales que se tratan en el espacio de programación de bajo nivel:
- Tipos,
byref/inref/outrefque son punteros administrados. Tienen restricciones de uso para que no se pueda compilar un programa que no sea válido en tiempo de ejecución. - Un struct similar a , que es una estructura que tiene una semántica similar y las mismas restricciones en tiempo
byrefde compilación quebyref<'T>. Un ejemplo es Span<T> .
Sintaxis
// Byref types as parameters
let f (x: byref<'T>) = ()
let g (x: inref<'T>) = ()
let h (x: outref<'T>) = ()
// Calling a function with a byref parameter
let mutable x = 3
f &x
// Declaring a byref-like struct
open System.Runtime.CompilerServices
[<Struct; IsByRefLike>]
type S(count1: int, count2: int) =
member x.Count1 = count1
member x.Count2 = count2
Byref, inref y outref
Hay tres formas de byref :
inref<'T>, un puntero administrado para leer el valor subyacente.outref<'T>, un puntero administrado para escribir en el valor subyacente.byref<'T>, un puntero administrado para leer y escribir el valor subyacente.
Se byref<'T> puede pasar donde se espera un inref<'T> . De forma similar, byref<'T> se puede pasar donde se espera un outref<'T> .
Uso de byrefs
Para usar inref<'T> un , debe obtener un valor de puntero con & :
open System
let f (dt: inref<DateTime>) =
printfn $"Now: %O{dt}"
let usage =
let dt = DateTime.Now
f &dt // Pass a pointer to 'dt'
Para escribir en el puntero mediante o , también debe hacer que outref<'T> el valor que se toma un puntero a byref<'T> mutable .
open System
let f (dt: byref<DateTime>) =
printfn $"Now: %O{dt}"
dt <- DateTime.Now
// Make 'dt' mutable
let mutable dt = DateTime.Now
// Now you can pass the pointer to 'dt'
f &dt
Si solo está escribiendo el puntero en lugar de leerlo, considere la posibilidad de usar outref<'T> en lugar de byref<'T> .
Semántica de inref
Observe el código siguiente:
let f (x: inref<SomeStruct>) = x.SomeField
Semánticamente, esto significa lo siguiente:
- El titular del
xpuntero solo puede usarlo para leer el valor. - Cualquier puntero adquirido a
structlos camposSomeStructanidados dentro de tiene el tipoinref<_>.
También se cumple lo siguiente:
- No hay ninguna implicación de que otros subprocesos o alias no tengan acceso de escritura a
x. - No hay ninguna implicación
SomeStructque sea inmutable en virtud dexserinref.
Sin embargo, para los tipos de valor de F# que son inmutables, el puntero se deduce this como inref .
Todas estas reglas juntas significan que el titular de un puntero puede no modificar el inref contenido inmediato de la memoria a la que se apunta.
Semántica de referencia externa
El propósito de outref<'T> es indicar que el puntero solo se debe escribir en . Inesperadamente, outref<'T> permite leer el valor subyacente a pesar de su nombre. Esto es por motivos de compatibilidad. Semánticamente, outref<'T> no es diferente de byref<'T> .
Interoperabilidad con C#
C# admite las in ref palabras clave y , además de out ref ref returns. En la tabla siguiente se muestra cómo F# interpreta lo que emite C#:
| Construcción de C# | Inferencias de F# |
|---|---|
ref valor devuelto |
outref<'T> |
ref readonly valor devuelto |
inref<'T> |
Parámetro in ref |
inref<'T> |
Parámetro out ref |
outref<'T> |
En la tabla siguiente se muestra lo que emite F#:
| Construcción de F# | Construcción emitida |
|---|---|
Argumento inref<'T> |
[In] atributo en argumento |
inref<'T> devolución |
modreq atributo en valor |
inref<'T> en la ranura o implementación abstracta |
modreq on argument o return |
Argumento outref<'T> |
[Out] atributo en argumento |
Reglas de sobrecarga y inferencia de tipos
El compilador de F# deduce un tipo inref<'T> en los casos siguientes:
- Parámetro de .NET o tipo de valor devuelto que tiene un
IsReadOnlyatributo . - Puntero
thisen un tipo de estructura que no tiene campos mutables. - Dirección de una ubicación de memoria derivada de otro
inref<_>puntero.
Cuando se toma una dirección implícita de , se prefiere una sobrecarga con un argumento de tipo a una sobrecarga con un inref SomeType argumento de tipo inref<SomeType> . Por ejemplo:
type C() =
static member M(x: System.DateTime) = x.AddDays(1.0)
static member M(x: inref<System.DateTime>) = x.AddDays(2.0)
static member M2(x: System.DateTime, y: int) = x.AddDays(1.0)
static member M2(x: inref<System.DateTime>, y: int) = x.AddDays(2.0)
let res = System.DateTime.Now
let v = C.M(res)
let v2 = C.M2(res, 4)
En ambos casos, las sobrecargas que toman System.DateTime se resuelven en lugar de las sobrecargas que toman inref<System.DateTime> .
Estructuras de tipo Byref
Además del byref / inref / outref anillo, puede definir sus propios structs que se pueden cumplir con byref la semántica similar a la de . Esto se hace con el IsByRefLikeAttribute atributo :
open System
open System.Runtime.CompilerServices
[<IsByRefLike; Struct>]
type S(count1: Span<int>, count2: Span<int>) =
member x.Count1 = count1
member x.Count2 = count2
IsByRefLike no implica Struct . Ambos deben estar presentes en el tipo.
Un byref struct "-like" en F# es un tipo de valor enlazado a la pila. Nunca se asigna en el montón administrado. Una estructura de tipo es útil para la programación de alto rendimiento, ya que se aplica con un conjunto de comprobaciones seguras sobre la duración byref y la no captura. Las reglas son:
- Se pueden usar como parámetros de función, parámetros de método, variables locales y devoluciones de método.
- No pueden ser miembros estáticos o de instancia de una clase o struct normal.
- No se pueden capturar mediante ninguna construcción de cierre
async(métodos o expresiones lambda). - No se pueden usar como parámetro genérico.
Este último punto es fundamental para la programación de estilo de canalización de F#, ya que es una función |> genérica que parametriza sus tipos de entrada. Esta restricción puede ser relajada en el futuro, ya que está insertada y no realiza ninguna llamada a funciones genéricas no insertadas |> en su cuerpo.
Aunque estas reglas restringen en gran medida el uso, lo hacen para cumplir la promesa de informática de alto rendimiento de una manera segura.
Byref devuelve
Los resultados byref de funciones o miembros de F# se pueden generar y consumir. Al consumir un byref método -returning, el valor se desreferencia implícitamente. Por ejemplo:
let squareAndPrint (data : byref<int>) =
let squared = data*data // data is implicitly dereferenced
printfn $"%d{squared}"
Para devolver un valor byref, la variable que contiene el valor debe ser mayor que el ámbito actual.
Además, para devolver byref, use &value (donde value es una variable que reside más tiempo que el ámbito actual).
let mutable sum = 0
let safeSum (bytes: Span<byte>) =
for i in 0 .. bytes.Length - 1 do
sum <- sum + int bytes[i]
&sum // sum lives longer than the scope of this function.
Para evitar la desreferencia implícita, como pasar una referencia a través de varias llamadas encadenadas, use &x (donde x es el valor).
También puede asignar directamente a un valor byref devuelto. Tenga en cuenta el siguiente programa (muy imperativo):
type C() =
let mutable nums = [| 1; 3; 7; 15; 31; 63; 127; 255; 511; 1023 |]
override _.ToString() = String.Join(' ', nums)
member _.FindLargestSmallerThan(target: int) =
let mutable ctr = nums.Length - 1
while ctr > 0 && nums[ctr] >= target do ctr <- ctr - 1
if ctr > 0 then &nums[ctr] else &nums[0]
[<EntryPoint>]
let main argv =
let c = C()
printfn $"Original sequence: %O{c}"
let v = &c.FindLargestSmallerThan 16
v <- v*2 // Directly assign to the byref return
printfn $"New sequence: %O{c}"
0 // return an integer exit code
Éste es el resultado:
Original sequence: 1 3 7 15 31 63 127 255 511 1023
New sequence: 1 3 7 30 31 63 127 255 511 1023
Ámbito de byrefs
Un let valor enlazado a no puede hacer que su referencia supere el ámbito en el que se definió. Por ejemplo, no se puede hacer lo siguiente:
let test2 () =
let x = 12
&x // Error: 'x' exceeds its defined scope!
let test () =
let x =
let y = 1
&y // Error: `y` exceeds its defined scope!
()
Esto le impide obtener resultados diferentes en función de si compila con optimizaciones o no.