Byrefs
Az F# két fő funkcióterülettel rendelkezik, amelyek az alacsony szintű programozás területén foglalkoznak:
- Azok a
byref
//inref
outref
típusok, amelyek felügyelt mutatók. A használat korlátozásokkal rendelkezik, így futásidőben nem állítható össze érvénytelen program. - A
byref
-like struct, amely egy olyan szerkezet , amely hasonló szemantikával és ugyanolyan fordítási időkorlátokkal rendelkezik, mintbyref<'T>
a . Ilyen például a .Span<T>
Syntax
// 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 és outref
A következők három formája byref
létezik:
inref<'T>
, egy felügyelt mutató az alapul szolgáló érték olvasásához.outref<'T>
, egy felügyelt mutató a mögöttes értékre való íráshoz.byref<'T>
, egy felügyelt mutató a mögöttes érték olvasásához és írásához.
A byref<'T>
továbbítható, ahol egy inref<'T>
várható. Hasonlóképpen, átadható egy byref<'T>
olyan érték, ahol a outref<'T>
várt érték szerepel.
Byrefs használata
Ha egy inref<'T>
mutatót szeretne használni, a következővel kell egy mutatóértéket &
lekérnie:
open System
let f (dt: inref<DateTime>) =
printfn $"Now: %O{dt}"
let usage =
let dt = DateTime.Now
f &dt // Pass a pointer to 'dt'
Ha egy vagy byref<'T>
több outref<'T>
elemet használva szeretne írni a mutatóra, akkor azt az értéket is meg kell adnia, amelybe mutable
mutatót fog.
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
Ha csak az egérmutatót írja ahelyett, hogy elolvassa, fontolja meg a helyett a byref<'T>
használatátoutref<'T>
.
Inref szemantikák
Tekintse meg az alábbi kódot:
let f (x: inref<SomeStruct>) = x.SomeField
Szemantikailag ez a következőket jelenti:
- A mutató tulajdonosa
x
csak az érték olvasására használhatja. - A beágyazott mezőkre
struct
beolvasottSomeStruct
egérmutatók típustinref<_>
kapnak.
A következő is igaz:
- Nincs jelentősége annak, hogy más szálak vagy aliasok nem rendelkeznek írási hozzáféréssel
x
. - Nincs olyan hatása, hogy
SomeStruct
nem módosítható, mert az,x
hogy egyinref
.
A nem módosítható F#-értéktípusok esetében azonban a this
mutató inref
egy .
Ezek a szabályok együttesen azt jelentik, hogy a mutató tulajdonosa inref
nem módosíthatja a mutatott memória közvetlen tartalmát.
Outref szemantikák
A cél outref<'T>
az, hogy jelezze, hogy a mutatót csak erre kell írni. Váratlanul lehetővé teszi a outref<'T>
mögöttes érték olvasását a neve ellenére. Ez kompatibilitási célokat szolgál.
Szemantikailag nem más, outref<'T>
mint byref<'T>
egy különbség: a paraméterekkel rendelkező outref<'T>
metódusok implicit módon egy tuple visszatérési típusba vannak felépítve, ugyanúgy, mint amikor egy metódust paraméterrel [<Out>]
hívnak meg.
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 with C#
A C# a visszatérés mellett a kulcsszavakat és out ref
a in ref
kulcsszavakat ref
is támogatja. Az alábbi táblázat bemutatja, hogyan értelmezi az F# a C# által kibocsátott adatokat:
C#-szerkezet | F#-következtetések |
---|---|
ref visszatérési érték |
outref<'T> |
ref readonly visszatérési érték |
inref<'T> |
in ref Paraméter |
inref<'T> |
out ref Paraméter |
outref<'T> |
Az alábbi táblázat azt mutatja be, hogy az F# mit bocsát ki:
F#-szerkezet | Kibocsátott szerkezet |
---|---|
inref<'T> argumentum |
[In] attribútum az argumentumon |
inref<'T> Vissza |
modreq attribútum az értéken |
inref<'T> absztrakciós pontban vagy implementációban |
modreq argumentumon vagy visszatérésen |
outref<'T> argumentum |
[Out] attribútum az argumentumon |
Következtetési és túlterhelési szabályok beírása
Az inref<'T>
F#-fordító a következő esetekben következtet egy típusra:
- Attribútummal rendelkező
IsReadOnly
.NET-paraméter vagy visszatérési típus. - Egy
this
olyan szerkezettípus mutatója, amely nem tartalmaz módosítható mezőket. - Egy másik
inref<_>
mutatóból származtatott memóriahely címe.
Amikor egy implicit címet inref
vesz fel, a típus SomeType
argumentumával rendelkező túlterhelést előnyben részesíti a túlterhelés egy típus inref<SomeType>
argumentumával. Példa:
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)
Mindkét esetben a túlterhelések a System.DateTime
túlterhelések inref<System.DateTime>
helyett feloldódnak.
Byref-szerű szerkezetek
A trió mellett byref
//inref
outref
saját szerkezeteket is meghatározhat, amelyek megfelelnek a byref
-szerű szemantikának. Ez a következő attribútummal IsByRefLikeAttribute történik:
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
nem azt jelenti , hogy Struct
. Mindkettőnek jelen kell lennie a típuson.
Az F# "byref
-szerű" szerkezete veremhez kötött értéktípus. A rendszer soha nem foglalja le a felügyelt halomra. A byref
-like struct hasznos a nagy teljesítményű programozáshoz, mivel erős ellenőrzésekkel kényszerítik ki az élettartamra és a nem rögzítésre vonatkozóan. A szabályok a következők:
- Használhatók függvényparaméterekként, metódusparaméterekként, helyi változókként, metódusvisszatérésekként.
- Nem lehetnek statikusak vagy példányok tagjai egy osztálynak vagy normál szerkezetnek.
- Nem rögzíthetők lezáró szerkezettel (
async
metódusokkal vagy lambdakifejezésekkel). - Ezek nem használhatók általános paraméterként.
Ez az utolsó pont kulcsfontosságú az F# folyamatstílusú programozáshoz, csakúgy, mint |>
egy általános függvény, amely paraméterezi a bemeneti típusokat. Ez a korlátozás a jövőben enyhíthető |>
, mivel beágyazott, és nem indít hívásokat a nem beágyazott általános függvényekre a szervezetben.
Bár ezek a szabályok erősen korlátozzák a használatot, azért teszik ezt, hogy biztonságosan teljesítsék a nagy teljesítményű számítástechnika ígéretét.
Byref visszaadja
Az F# függvényekből vagy tagokból származó Byref-visszatérések előállíthatók és felhasználhatók. -returning metódus használata byref
esetén az érték implicit módon hareferens lesz. Példa:
let squareAndPrint (data : byref<int>) =
let squared = data*data // data is implicitly dereferenced
printfn $"%d{squared}"
Az érték byref visszaadásához az értéket tartalmazó változónak hosszabb ideig kell élnie, mint az aktuális hatókör.
A byref visszaadásához használja &value
is (ahol az érték egy olyan változó, amely hosszabb ideig él, mint az aktuális hatókör).
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.
Az implicit hareferencia elkerülése érdekében, például ha több láncolt híváson keresztül továbbít egy hivatkozást, használja &x
(hol x
van az érték).
A visszatéréshez byref
közvetlenül is hozzárendelhet . Fontolja meg a következő (rendkívül imperatív) programot:
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
Ez a kimenet:
Original sequence: 1 3 7 15 31 63 127 255 511 1023
New sequence: 1 3 7 30 31 63 127 255 511 1023
Hatókörkezelés byrefs esetén
A let
kötött értékek hivatkozása nem haladhatja meg azt a hatókört, amelyben definiálták. A következők például nem engedélyezettek:
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!
()
Ez megakadályozza, hogy különböző eredményeket kap, attól függően, hogy optimalizálással fordít-e vagy sem.