Bagikan melalui


Byrefs

F# memiliki dua area fitur utama yang berurusan dengan ruang pemrograman tingkat rendah:

  • Jenis, byref//inrefoutref yang merupakan penunjuk terkelola. Mereka memiliki batasan penggunaan sehingga Anda tidak dapat mengkompilasi program yang tidak valid pada waktu proses.
  • Struct byrefseperti, yang merupakan struktur yang memiliki semantik serupa dan pembatasan waktu kompilasi yang sama dengan byref<'T>. Salah satu contohnya adalah Span<T>.

Sintaksis

// 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, dan outref

Ada tiga bentuk :byref

  • inref<'T>, pointer terkelola untuk membaca nilai yang mendasar.
  • outref<'T>, pointer terkelola untuk menulis ke nilai yang mendasar.
  • byref<'T>, pointer terkelola untuk membaca dan menulis nilai yang mendasar.

byref<'T> Dapat diteruskan di mana diharapkaninref<'T>. Demikian pula, dapat byref<'T> dilewati di mana diharapkan outref<'T> .

Menggunakan byrefs

Untuk menggunakan inref<'T>, Anda perlu mendapatkan nilai pointer dengan &:

open System

let f (dt: inref<DateTime>) =
    printfn $"Now: %O{dt}"

let usage =
    let dt = DateTime.Now
    f &dt // Pass a pointer to 'dt'

Untuk menulis ke pointer dengan menggunakan outref<'T> atau byref<'T>, Anda juga harus membuat nilai yang Anda ambil pointer ke 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

Jika Anda hanya menulis pointer alih-alih membacanya, pertimbangkan untuk menggunakan outref<'T> alih-alih byref<'T>.

Semantik inref

Pertimbangkan kode berikut:

let f (x: inref<SomeStruct>) = x.SomeField

Secara semantik, ini berarti sebagai berikut:

  • Pemegang x pointer hanya dapat menggunakannya untuk membaca nilai .
  • Setiap penunjuk yang diperoleh ke struct bidang yang ditumpuk di dalamnya SomeStruct diberi jenis inref<_>.

Berikut ini juga benar:

  • Tidak ada implikasi bahwa utas atau alias lain tidak memiliki akses tulis ke x.
  • Tidak ada implikasi yang SomeStruct tidak dapat diubah berdasarkan kebajikan x menjadi .inref

Namun, untuk jenis nilai F# yang tidak dapat diubah, this pointer disimpulkan sebagai inref.

Semua aturan ini bersama-sama berarti bahwa pemegang inref pointer mungkin tidak memodifikasi konten langsung memori yang ditunjukkan.

Semantik outref

Tujuannya outref<'T> adalah untuk menunjukkan bahwa pointer hanya boleh ditulis. Secara tak terduga, outref<'T> mengizinkan membaca nilai yang mendasar meskipun namanya. Ini untuk tujuan kompatibilitas.

Secara semantik, outref<'T> tidak berbeda dari byref<'T>, kecuali untuk satu perbedaan: metode dengan outref<'T> parameter secara implisit dibangun menjadi jenis pengembalian tuple, seperti saat memanggil metode dengan [<Out>] parameter.

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 dengan C#

C# mendukung in ref kata kunci dan out ref , selain ref mengembalikan. Tabel berikut menunjukkan bagaimana F# menginterpretasikan apa yang dipancarkan C#:

Konstruksi C# Infersi F#
ref mengembalikan nilai outref<'T>
ref readonly mengembalikan nilai inref<'T>
parameter in ref inref<'T>
parameter out ref outref<'T>

Tabel berikut ini memperlihatkan apa yang dipancarkan F#:

Konstruksi F# Konstruksi yang dipancarkan
Argumen inref<'T> [In] atribut pada argumen
inref<'T> kembali modreq atribut pada nilai
inref<'T> dalam slot atau implementasi abstrak modreq pada argumen atau pengembalian
Argumen outref<'T> [Out] atribut pada argumen

Ketik aturan inferensi dan kelebihan beban

Jenis inref<'T> disimpulkan oleh pengkompilasi F# dalam kasus berikut:

  1. Parameter .NET atau jenis pengembalian yang memiliki IsReadOnly atribut.
  2. Penunjuk this pada jenis struct yang tidak memiliki bidang yang dapat diubah.
  3. Alamat lokasi memori yang berasal dari pointer lain inref<_> .

Ketika alamat implisit dari suatu inref sedang diambil, kelebihan beban dengan argumen jenis SomeType lebih disukai daripada kelebihan beban dengan argumen jenis inref<SomeType>. Contohnya:

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)

Dalam kedua kasus, kelebihan beban yang diambil System.DateTime diselesaikan daripada kelebihan beban mengambil inref<System.DateTime>.

Struktur seperti byref

Selain byref//inrefoutref trio, Anda dapat menentukan struktur Anda sendiri yang dapat mematuhi byref-seperti semantik. Ini dilakukan dengan atribut 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 tidak menyiratkan Struct. Keduanya harus ada pada jenis .

Struktur "byrefseperti" di F# adalah jenis nilai yang terikat tumpukan. Ini tidak pernah dialokasikan pada tumpukan terkelola. Struktur byrefseperti ini berguna untuk pemrograman berkinerja tinggi, karena diberlakukan dengan serangkaian pemeriksaan yang kuat tentang masa pakai dan non-pengambilan. Aturannya adalah:

  • Mereka dapat digunakan sebagai parameter fungsi, parameter metode, variabel lokal, pengembalian metode.
  • Mereka tidak boleh menjadi anggota statis atau instans kelas atau struktur normal.
  • Mereka tidak dapat ditangkap oleh konstruksi penutupan apa pun (async metode atau ekspresi lambda).
  • Mereka tidak dapat digunakan sebagai parameter generik.
    • Dimulai dengan F# 9, pembatasan ini dilonggarkan jika parameter generik didefinisikan dalam C# menggunakan anti-batasan struct memungkinkan ref. F# dapat membuat instans generik tersebut dalam jenis dan metode dengan jenis seperti byref. Sebagai beberapa contoh, ini memengaruhi jenis delegasi BCL (Action<>, Func<>), antarmuka (IEnumerable<>, IComparable<>) dan argumen generik dengan fungsi akumulator yang disediakan pengguna (String.string Create<TState>(int length, TState state, SpanAction<char, TState> action)).
    • Tidak mungkin untuk menulis kode generik yang mendukung jenis seperti byref di F#.

Poin terakhir ini sangat penting untuk pemrograman gaya alur F#, seperti |> halnya fungsi generik yang membuat parameter jenis inputnya. Pembatasan ini dapat dilonggarkan untuk |> di masa depan, karena sebaris dan tidak melakukan panggilan apa pun ke fungsi generik yang tidak bergaris dalam tubuhnya.

Meskipun aturan ini sangat membatasi penggunaan, mereka melakukannya untuk memenuhi janji komputasi berkinerja tinggi dengan cara yang aman.

Byref mengembalikan

Byref mengembalikan dari fungsi F# atau anggota dapat diproduksi dan dikonsumsi. Saat mengonsumsi byrefmetode -returning, nilai secara implisit didereferensikan. Contohnya:

let squareAndPrint (data : byref<int>) =
    let squared = data*data    // data is implicitly dereferenced
    printfn $"%d{squared}"

Untuk mengembalikan byref nilai, variabel yang berisi nilai harus hidup lebih lama dari cakupan saat ini. Selain itu, untuk mengembalikan byref, gunakan &value (di mana nilai adalah variabel yang hidup lebih lama dari cakupan saat ini).

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.

Untuk menghindari dereferensi implisit, seperti meneruskan referensi melalui beberapa panggilan berantai, gunakan &x (di mana x adalah nilainya).

Anda juga dapat langsung menetapkan ke pengembalian byref. Pertimbangkan program berikut (sangat penting):

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

Ini adalah output:

Original sequence: 1 3 7 15 31 63 127 255 511 1023
New sequence:      1 3 7 30 31 63 127 255 511 1023

Cakupan untuk byref

Nilai let-bound tidak dapat memiliki referensi melebihi cakupan di mana nilai tersebut ditentukan. Misalnya, berikut ini tidak diizinkan:

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!
    ()

Ini mencegah Anda mendapatkan hasil yang berbeda tergantung pada apakah Anda mengkompilasi dengan pengoptimalan atau tidak.