Byrefs

F# memiliki dua area fitur utama yang berada di ruang pemrograman tingkat rendah:

  • Jenis byref/inref/outref, yang merupakan pointer terkelola. Jenis-jenis ini memiliki batasan penggunaan sehingga Anda tidak dapat mengompilasi program yang tidak valid pada durasi.
  • Struktur mirip byref, yang merupakan struktur yang memiliki semantik serupa dan pembatasan waktu kompilasi yang sama dengan byref<'T>. Salah satu contohnya adalah Span<T>.

Sintaks

// 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 dari 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 dilewati di mana inref<'T> diharapkan. Demikian juga, byref<'T> dapat dilewati di mana outref<'T> diharapkan.

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 sebagai 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 tanpa membacanya, pertimbangkan untuk menggunakan outref<'T> daripada byref<'T>.

Semantik inref

Pertimbangkan gambar berikut:

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

Secara semantik, ini berarti sebagai berikut:

  • Pemegang pointer x hanya dapat menggunakannya untuk membaca nilai.
  • Pointer apa pun yang diperoleh ke bidang struct yang disarangkan di dalam SomeStruct diberi jenis inref<_>.

Berikut ini juga benar:

  • Tidak ada implikasi bahwa alur atau alias lain tidak memiliki akses tulis ke x.
  • Tidak ada implikasi bahwa SomeStruct tidak dapat diubah karena x merupakan inref.

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

Seluruh aturan ini berarti bahwa pemegang pointer inref mungkin tidak dapat memodifikasi konten langsung dari memori yang ditunjuk.

Semantik outref

Tujuan outref<'T> adalah untuk menunjukkan bahwa pointer hanya boleh ditulis. Secara tidak terduga, outref<'T> mengizinkan pembacaan nilai yang mendasar terlepas dari namanya. Ini untuk tujuan kompatibilitas.

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

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 kata kunci in ref dan out ref, selain pengembalian ref. Tabel berikut ini menunjukkan bagaimana F# menafsirkan apa yang dikeluarkan C#:

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

Tabel berikut ini menunjukkan apa yang dikeluarkan F#:

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

Inferensi jenis dan aturan kelebihan beban

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

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

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

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, pengambilan kelebihan beban System.DateTime lebih dapat diselesaikan daripada pengambilan kelebihan beban inref<System.DateTime>.

Struktur mirip-byref

Selain trio byref/inref/outref, Anda dapat menentukan struktur Anda sendiri yang dapat mengikuti semantik mirip-byref. Hal 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 jenisnya.

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

  • Dapat digunakan sebagai parameter fungsi, parameter metode, variabel lokal, dan pengembalian metode.
  • Tidak boleh menjadi anggota statis atau instans dari suatu kelas atau struktur normal.
  • Tidak dapat diambil oleh konstruksi penutupan apa pun (metode async atau ekspresi lambda).
  • Tidak dapat digunakan sebagai parameter generik.

Titik terakhir ini sangat penting untuk pemrograman gaya alur F# karena |> merupakan fungsi generik yang menentukan parameter jenis inputnya. Pembatasan ini dapat dilonggarkan untuk |> di masa depan karena sebaris dan tidak melakukan panggilan apa pun ke fungsi generik yang tidak sebaris dalam isinya.

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

Pengembalian byref

Byref yang dikembalikan dari fungsi atau anggota F# dapat diproduksi dan digunakan. Saat menggunakan metode pengembalian byref, nilainya secara implisit didereferensikan. Misalnya:

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 berlangsung lebih lama dari cakupan saat ini. Selain itu, untuk mengembalikan byref, gunakan &value (yang nilainya merupakan variabel yang berlangsung 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 (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 outputnya:

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 terikat let tidak boleh memiliki referensi melebihi cakupan yang ditentukan. Misalnya, hal-hal berikut tidak diperbolehkan:

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

Hal ini mencegah Anda untuk mendapatkan hasil yang berbeda, tergantung pada apakah Anda mengompilasi dengan pengoptimalan atau tidak.