Byref
F#에는 하위 수준 프로그래밍 공간을 다루는 두 가지 주요 기능 영역이 있습니다.
byref
//inref
outref
관리되는 포인터인 형식입니다. 런타임에 잘못된 프로그램을 컴파일할 수 없도록 사용 제한이 있습니다.byref
의미 체계와 컴파일 시간 제한이byref<'T>
동일한 구조체인 -like 구조체입니다. 한 가지 예는 .입니다 Span<T>.
구문
// 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 및 outref
다음과 같은 세 가지 형식이 byref
있습니다.
inref<'T>
- 기본 값을 읽기 위한 관리되는 포인터입니다.outref<'T>
- 기본 값에 쓰기 위한 관리되는 포인터입니다.byref<'T>
- 기본 값을 읽고 쓰기 위한 관리되는 포인터입니다.
A byref<'T>
는 필요한 위치에 inref<'T>
전달할 수 있습니다. 마찬가지로, byref<'T>
예상되는 위치에 outref<'T>
전달할 수 있습니다.
byrefs 사용
를 사용하려면 다음을 inref<'T>
사용하여 포인터 값을 &
가져와야 합니다.
open System
let f (dt: inref<DateTime>) =
printfn $"Now: %O{dt}"
let usage =
let dt = DateTime.Now
f &dt // Pass a pointer to 'dt'
또는 byref<'T>
을 사용하여 포인터에 쓰려면 포인터mutable
를 outref<'T>
잡는 값도 만들어야 합니다.
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
포인터를 읽는 대신 포인터만 작성하는 경우 대신 사용하는 outref<'T>
byref<'T>
것이 좋습니다.
Inref 의미 체계
다음 코드를 생각해 봅시다.
let f (x: inref<SomeStruct>) = x.SomeField
의미상 다음을 의미합니다.
- 포인터의 소유자만
x
값을 읽는 데 사용할 수 있습니다. - 중
SomeStruct
첩된 필드에 획득된struct
모든 포인터에는 형식inref<_>
이 지정됩니다.
다음도 마찬가지입니다.
- 다른 스레드 또는 별칭에 대한 쓰기 권한이
x
없다는 의미는 없습니다. - 덕에
SomeStruct
변경할 수 없는x
inref
의미는 없습니다.
그러나 변경할 수 없는 F# 값 형식 의 this
경우 포인터가 .로 유추됩니다inref
.
이러한 모든 규칙은 포인터 소유자가 가리키는 메모리의 inref
즉각적인 내용을 수정하지 않을 수 있음을 의미합니다.
Outref 의미 체계
목적은 outref<'T>
포인터만 작성해야 함을 나타내는 것입니다. 예기치 않게 이름 outref<'T>
에도 불구하고 기본 값을 읽을 수 있습니다. 이는 호환성을 위한 것입니다.
의미 체계는 outref<'T>
한 가지 차이점을 제외하고 다르지 byref<'T>
않습니다. 매개 변수가 있는 메서드는 매개 변수를 사용하여 outref<'T>
메서드를 호출할 때와 마찬가지로 암시적으로 튜플 반환 형식으로 [<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"
C와의 Interop#
C#은 in ref
out ref
반환 외에도 ref
키워드(keyword) 지원합니다. 다음 표에서는 F#이 C#이 내보내는 내용을 해석하는 방법을 보여 줍니다.
C# 구문 | F# 유추 |
---|---|
ref 반환 값 |
outref<'T> |
ref readonly 반환 값 |
inref<'T> |
in ref 매개 변수 |
inref<'T> |
out ref 매개 변수 |
outref<'T> |
다음 표에서는 F#이 내보내는 내용을 보여 줍니다.
F# 구문 | 내보낸 구문 |
---|---|
inref<'T> 인수 |
[In] 인수의 특성 |
inref<'T> 반환 |
modreq 값에 대한 특성 |
inref<'T> 추상 슬롯 또는 구현 |
modreq on argument or return |
outref<'T> 인수 |
[Out] 인수의 특성 |
형식 유추 및 오버로드 규칙
inref<'T>
형식은 다음 경우에 F# 컴파일러에 의해 유추됩니다.
- 특성이 있는 .NET 매개 변수 또는 반환 형식입니다
IsReadOnly
. this
변경할 수 있는 필드가 없는 구조체 형식의 포인터입니다.- 다른
inref<_>
포인터에서 파생된 메모리 위치의 주소입니다.
암시적 주소를 inref
사용하는 경우 형식 인수가 있는 오버로드는 형식 SomeType
인수가 있는 오버로드 inref<SomeType>
를 사용하는 것이 좋습니다. 예시:
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)
두 경우 모두 오버로드를 사용하는 System.DateTime
대신 오버로드가 확인됩니다 inref<System.DateTime>
.
Byref와 유사한 구조체
트리오 외에도 byref
//inref
outref
같은 의미 체계를 준수할 수 있는 고유한 구조체를 정의할 byref
수 있습니다. 이 작업은 특성으로 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
은 암시 Struct
하지 않습니다. 둘 다 형식에 있어야 합니다.
F#의 "byref
유사" 구조체는 스택 바인딩된 값 형식입니다. 관리되는 힙에는 할당되지 않습니다. byref
-like 구조체는 수명 및 비 캡처에 대한 강력한 검사 집합으로 적용되므로 고성능 프로그래밍에 유용합니다. 규칙은 다음과 같습니다.
- 함수 매개 변수, 메서드 매개 변수, 지역 변수, 메서드 반환으로 사용할 수 있습니다.
- 클래스 또는 일반 구조체의 정적 또는 인스턴스 멤버일 수 없습니다.
- 클로저 구문(
async
메서드 또는 람다 식)으로 캡처할 수 없습니다. - 제네릭 매개 변수로 사용할 수 없습니다.
이 마지막 지점은 입력 형식을 매개 변수화하는 제네릭 함수와 마찬가지로 |>
F# 파이프라인 스타일 프로그래밍에 매우 중요합니다. 이 제한은 인라인이며 본문에 인라인되지 않은 제네릭 함수를 호출하지 않으므로 나중에 완화 |>
될 수 있습니다.
이러한 규칙은 사용을 강력하게 제한하지만 안전한 방식으로 고성능 컴퓨팅의 약속을 이행합니다.
Byref 반환
F# 함수 또는 멤버에서 Byref 반환을 생성하고 사용할 수 있습니다. -returning 메서드를 byref
사용할 때 값은 암시적으로 역참조됩니다. 예시:
let squareAndPrint (data : byref<int>) =
let squared = data*data // data is implicitly dereferenced
printfn $"%d{squared}"
값 바이레프를 반환하려면 값이 포함된 변수가 현재 범위보다 오래 있어야 합니다.
또한 byref를 반환하려면 값이 현재 범위보다 오래 사는 변수인 경우를 사용합니다 &value
.
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.
여러 연결 호출을 통해 참조를 전달하는 것과 같은 암시적 역참조를 방지하려면(값은 어디에) x
사용합니다 &x
.
반환 byref
에 직접 할당할 수도 있습니다. 다음(매우 명령적) 프로그램을 고려합니다.
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
출력은 다음과 같습니다.
Original sequence: 1 3 7 15 31 63 127 255 511 1023
New sequence: 1 3 7 30 31 63 127 255 511 1023
바이레프 범위 지정
-bound 값은 let
해당 참조가 정의된 범위를 초과할 수 없습니다. 예를 들어 다음은 허용되지 않습니다.
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!
()
이렇게 하면 최적화를 사용하여 컴파일하는지 여부에 따라 다른 결과를 얻을 수 없습니다.
.NET