Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
F# a deux domaines de fonctionnalités majeurs qui traitent de l’espace de programmation de bas niveau :
- Types
byref//inrefoutref, qui sont des pointeurs managés. Ils ont des restrictions sur l’utilisation afin que vous ne puissiez pas compiler un programme non valide au moment de l’exécution. - Un
byrefstruct similaire, qui est un struct qui a une sémantique similaire et les mêmes restrictions au moment de la compilation quebyref<'T>. Voici un exemple Span<T>: .
Syntaxe
// 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 et outref
Il existe trois formes de byref:
-
inref<'T>, pointeur managé pour lire la valeur sous-jacente. -
outref<'T>, pointeur managé pour l’écriture dans la valeur sous-jacente. -
byref<'T>, pointeur managé pour la lecture et l’écriture de la valeur sous-jacente.
Un byref<'T> peut être passé à l’endroit où une inref<'T> valeur est attendue. De même, un byref<'T> peut être transmis là où un outref<'T> est attendu.
Utilisation de byrefs
Pour utiliser un inref<'T>, vous devez obtenir une valeur de pointeur avec &:
open System
let f (dt: inref<DateTime>) =
printfn $"Now: %O{dt}"
let usage =
let dt = DateTime.Now
f &dt // Pass a pointer to 'dt'
Pour écrire dans le pointeur à l’aide d’un outref<'T> ou byref<'T>, vous devez également rendre la valeur vers laquelle vous récupérez un pointeur 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 vous écrivez uniquement le pointeur au lieu de le lire, envisagez d’utiliser outref<'T> au lieu de byref<'T>.
Sémantique d’inref
Considérez le code suivant :
let f (x: inref<SomeStruct>) = x.SomeField
Sémantiquement, cela signifie ce qui suit :
- Le titulaire du
xpointeur peut uniquement l’utiliser pour lire la valeur. - Tout pointeur acquis vers
structdes champs imbriqués dansSomeStructle typeinref<_>est donné.
Voici également la valeur true :
- Il n’y a aucune implication que d’autres threads ou alias n’ont pas d’accès en écriture à
x. - Il n’y a aucune implication immuable
SomeStructen vertu d’êtrexuninref.
Toutefois, pour les types de valeurs F# immuables, le this pointeur est déduit comme étant un inref.
Toutes ces règles signifient ensemble que le titulaire d’un inref pointeur peut ne pas modifier le contenu immédiat de la mémoire vers laquelle il pointe.
Sémantique Outref
L’objectif est d’indiquer outref<'T> que le pointeur doit uniquement être écrit. De façon inattendue, outref<'T> permet de lire la valeur sous-jacente malgré son nom. Cela est destiné à des fins de compatibilité.
Sémantiquement, outref<'T> n’est pas différent de byref<'T>, à l’exception d’une différence : les méthodes avec outref<'T> des paramètres sont implicitement construites dans un type de retour tuple, comme lors de l’appel d’une méthode avec un [<Out>] paramètre.
type C =
static member M1(x, y: _ outref) =
y <- x
true
match C.M1 1 with
| true, 1 -> printfn "Expected" // Fine with outref, error with byref
| _ -> printfn "Never matched"
Interopérabilité avec C#
C# prend en charge les mots clés et out ref les in ref mots clés, en plus des ref retours. Le tableau suivant montre comment F# interprète ce qu’émet C# :
| Construction C# | Inférences F# |
|---|---|
ref valeur de retour |
outref<'T> |
ref readonly valeur de retour |
inref<'T> |
Paramètre in ref |
inref<'T> |
Paramètre out ref |
outref<'T> |
Le tableau suivant montre ce que L#F# émet :
| Construction F# | Construction émise |
|---|---|
Argument inref<'T> |
[In] attribut sur l’argument |
inref<'T> rendre |
modreq attribut sur la valeur |
inref<'T> dans l’emplacement ou l’implémentation abstraits |
modreq sur l’argument ou le retour |
Argument outref<'T> |
[Out] attribut sur l’argument |
Règles d’inférence et de surcharge de type
Un inref<'T> type est déduit par le compilateur F# dans les cas suivants :
- Paramètre .NET ou type de retour qui a un
IsReadOnlyattribut. - Pointeur
thissur un type de struct qui n’a aucun champ mutable. - Adresse d’un emplacement de mémoire dérivé d’un autre
inref<_>pointeur.
Lorsqu’une adresse implicite d’un élément inref est prise, une surcharge avec un argument de type SomeType est préférée à une surcharge avec un argument de type inref<SomeType>. Par exemple:
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)
Dans les deux cas, les surcharges prises System.DateTime sont résolues plutôt que les surcharges prises inref<System.DateTime>.
Structs de type Byref
En plus du byref//inrefoutref trio, vous pouvez définir vos propres structs qui peuvent adhérer à byrefla sémantique .like. Pour ce faire, utilisez l’attribut IsByRefLikeAttribute :
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 n’implique Structpas . Les deux doivent être présents sur le type.
Un struct «byref like » en F# est un type de valeur lié à la pile. Il n’est jamais alloué sur le tas managé. Un byrefstruct semblable est utile pour la programmation hautes performances, car il est appliqué avec un ensemble de vérifications fortes sur la durée de vie et la non-capture. Les règles sont les suivantes :
- Ils peuvent être utilisés comme paramètres de fonction, paramètres de méthode, variables locales, retours de méthode.
- Ils ne peuvent pas être des membres statiques ou d’instance d’une classe ou d’un struct normal.
- Ils ne peuvent pas être capturés par une construction de fermeture (
asyncméthodes ou expressions lambda). - Ils ne peuvent pas être utilisés comme paramètre générique.
- À compter de F# 9, cette restriction est assouplie si le paramètre générique est défini en C# à l’aide de l’anti-contrainte de structure ref. F# peut instancier ces génériques dans les types et méthodes avec des types de type parref. Dans quelques exemples, cela affecte les types de délégués BCL (
Action<>,Func<>), les interfaces (IEnumerable<>,IComparable<>) et les arguments génériques avec une fonction d’accumulation fournie par l’utilisateur (String.string Create<TState>(int length, TState state, SpanAction<char, TState> action)). - Il est impossible de créer du code générique prenant en charge des types de type typeref dans F#.
- À compter de F# 9, cette restriction est assouplie si le paramètre générique est défini en C# à l’aide de l’anti-contrainte de structure ref. F# peut instancier ces génériques dans les types et méthodes avec des types de type parref. Dans quelques exemples, cela affecte les types de délégués BCL (
Ce dernier point est crucial pour la programmation de style pipeline F#, car il |> s’agit d’une fonction générique qui paramétre ses types d’entrée. Cette restriction peut être assouplie à |> l’avenir, car elle est inline et n’effectue aucun appel à des fonctions génériques non incorporées dans son corps.
Bien que ces règles restreignent fortement l’utilisation, elles le font pour répondre à la promesse d’un calcul hautes performances de manière sécurisée.
Byref retourne
Les retours Parref des fonctions ou membres F# peuvent être produits et consommés. Lors de l’utilisation d’une byrefméthode -returning, la valeur est implicitement déréférente. Par exemple:
let squareAndPrint (data : byref<int>) =
let squared = data*data // data is implicitly dereferenced
printfn $"%d{squared}"
Pour retourner une valeur parref, la variable qui contient la valeur doit vivre plus longtemps que l’étendue actuelle.
En outre, pour retourner byref, utilisez &value (où la valeur est une variable qui vit plus longtemps que l’étendue actuelle).
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.
Pour éviter la déréférencement implicite, telle que la transmission d’une référence via plusieurs appels chaînés, utilisez &x (où x se trouve la valeur).
Vous pouvez également affecter directement à un retour byref. Considérez le programme suivant (fortement impératif) :
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
Voici le résultat :
Original sequence: 1 3 7 15 31 63 127 255 511 1023
New sequence: 1 3 7 30 31 63 127 255 511 1023
Étendue pour les byrefs
Une letvaleur liée ne peut pas avoir sa référence dépasse la portée dans laquelle elle a été définie. Par exemple, les éléments suivants ne sont pas autorisés :
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!
()
Cela vous empêche d’obtenir des résultats différents selon que vous compilez avec des optimisations ou non.