Parametry ByRef

Jazyk F# má dvě hlavní oblasti funkcí, které se zabývají prostorem programování nízké úrovně:

  • Typybyref//inrefoutref, které jsou spravované ukazatele. Mají omezení použití, takže nelze zkompilovat program, který je v době běhu neplatný.
  • Struktura byref-like, což je struktura , která má podobnou sémantiku a stejná omezení času kompilace jako byref<'T>. Jedním z příkladů je 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 a outref

Existují tři formy byref:

  • inref<'T>, spravovaný ukazatel pro čtení podkladové hodnoty.
  • outref<'T>, spravovaný ukazatel pro zápis do podkladové hodnoty.
  • byref<'T>, spravovaný ukazatel pro čtení a zápis podkladové hodnoty.

Lze byref<'T> předat, kde je očekáváno inref<'T> . byref<'T> Podobně lze předat, kde outref<'T> je očekáváno.

Použití byrefs

Chcete-li použít inref<'T>, musíte získat hodnotu ukazatele s &:

open System

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

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

Chcete-li zapisovat na ukazatel pomocí nebo outref<'T>byref<'T>, musíte také nastavit hodnotu, na mutablekterou chytnete ukazatel .

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

Pokud píšete pouze ukazatel místo čtení, zvažte použití outref<'T> namísto byref<'T>.

Sémantika inref

Uvažujte následující kód:

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

Séanticky to znamená následující:

  • Držitel x ukazatele jej může použít pouze ke čtení hodnoty.
  • Libovolný ukazatel získaný na struct pole vnořená do SomeStruct daného typu inref<_>.

Platí také toto:

  • Neexistuje žádný implikace, že jiné vlákna nebo aliasy nemají přístup k zápisu .x
  • Není žádný implikace, která SomeStruct je neměnná na základě x toho, že je .inref

U typů hodnot jazyka F#, které jsou neměnné, this je však ukazatel odvozen jako .inref

Všechna tato pravidla společně znamenají, že držitel inref ukazatele nesmí upravovat okamžitý obsah paměti, na kterou odkazuje.

Sémantika outref

Účelem outref<'T> je označit, že ukazatel by měl být zapsán pouze do. Neočekávaně outref<'T> umožňuje čtení podkladové hodnoty bez ohledu na jeho název. To je pro účely kompatibility.

Sémanticky, outref<'T> není jiné než byref<'T>, s výjimkou jednoho rozdílu: metody s outref<'T> parametry jsou implicitně sestaveny do návratového typu řazené kolekce členů, stejně jako při volání metody s parametrem [<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"

Spolupráce s jazykem C#

Jazyk C# kromě vrácení podporuje in refout ref i ref klíčová slova. Následující tabulka ukazuje, jak jazyk F# interpretuje, co jazyk C# generuje:

Konstruktor jazyka C# Odvození jazyka F#
ref návratová hodnota outref<'T>
ref readonly návratová hodnota inref<'T>
in ref Parametr inref<'T>
out ref Parametr outref<'T>

Následující tabulka ukazuje, co jazyk F# generuje:

Konstrukce jazyka F# Generovaný konstruktor
Argument inref<'T> [In] atribut v argumentu
inref<'T> Vrátit modreq atribut pro hodnotu
inref<'T> v abstraktním slotu nebo implementaci modreq při argumentu nebo vrácení
Argument outref<'T> [Out] atribut v argumentu

Pravidla odvozování typů a přetížení

Typ inref<'T> je odvozen kompilátorem jazyka F# v následujících případech:

  1. Parametr .NET nebo návratový IsReadOnly typ, který má atribut.
  2. Ukazatel this na typ struktury, který nemá žádná proměnlivá pole.
  3. Adresa umístění paměti odvozené z jiného inref<_> ukazatele.

Pokud je přijata implicitní adresa, inref přetížení s argumentem typu SomeType je upřednostňované pro přetížení s argumentem typu inref<SomeType>. Příklad:

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)

V oboupřípadechch System.DateTimeinref<System.DateTime>

Byref-like struktury

Kromě byref//inrefoutref trojice můžete definovat vlastní struktury, které mohou dodržovat byrefsémantiku -like. To se provádí pomocí atributu 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 neznamená Struct. Oba musí být přítomné na typu.

Struktura "byref-like" v jazyce F# je typ hodnoty vázané na zásobník. Na spravované haldě se nikdy nepřiděluje. Struktura byrefpodobná je užitečná pro programování s vysokým výkonem, protože se vynucuje se sadou silných kontrol o životnosti a nezachytávání. Pravidla jsou:

  • Mohou být použity jako parametry funkce, parametry metody, místní proměnné, metoda vrací.
  • Nemohou být statické nebo instance členy třídy nebo normální struktury.
  • Nelze je zachytit žádným konstruktorem uzavření (async metodami nebo výrazy lambda).
  • Nelze je použít jako obecný parametr.

Tento poslední bod je zásadní pro programování ve stylu kanálu F#, stejně jako |> obecná funkce, která parametrizuje své vstupní typy. Toto omezení může být v budoucnu uvolněné |> , protože je vložené a neprovádí žádná volání neschycených obecných funkcí v těle.

I když tato pravidla důrazně omezují využití, dělají to tak, aby splňovaly příslib vysoce výkonného výpočetního prostředí bezpečným způsobem.

Byref vrátí

Funkce Byref vrací z funkcí jazyka F# nebo členů lze vytvořit a využívat. Při využívání byrefmetody -returning je hodnota implicitně dereferenced. Příklad:

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

Pokud chcete vrátit hodnotu byref, proměnná, která obsahuje hodnotu, musí být aktivní déle než aktuální obor. Pokud chcete vrátit hodnotu byref, použijte &value hodnotu (kde hodnota je proměnná, která je delší než aktuální obor).

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.

Pokud se chcete vyhnout implicitní dereference, například předání odkazu prostřednictvím více zřetězených volání, použijte &x (kde x je hodnota).

Můžete také přímo přiřadit k návratu byref. Zvažte následující (vysoce imperativní) program:

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

Toto je výstup:

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

Vymezení rozsahu pro byrefs

Hodnota letvázaná na hodnotu nesmí mít odkaz větší než obor, ve kterém byla definována. Například následující příkaz je zakázán:

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

To vám zabrání v získání různých výsledků v závislosti na tom, jestli je zkompilujete s optimalizacemi nebo ne.