Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
F# имеет две основные области функций, которые занимаются в пространстве низкоуровневого программирования:
-
byref/ /inrefoutrefТипы, которые являются управляемыми указателями. Они имеют ограничения на использование, чтобы вы не скомпилировали программу, которая является недопустимой во время выполнения. - Например
byref, структуру , которая имеет аналогичную семантику и те же ограничения времени компиляции, чтоbyref<'T>и структуры. Один из примеров: 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>— управляемый указатель для чтения и записи базового значения.
Можно передать, 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'
Чтобы записать указатель на указатель с помощью outref<'T> или byref<'T>, необходимо также сделать значение, на 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
Если вы пишете указатель, а не читаете его, рекомендуется использовать outref<'T> вместо byref<'T>него.
Семантика inref
Рассмотрим следующий код:
let f (x: inref<SomeStruct>) = x.SomeField
Семантически это означает следующее:
- Владелец указателя
xможет использовать его только для чтения значения. - Любой указатель, полученный в
structполях, вложенных в пределахSomeStruct, задан типinref<_>.
Ниже приведено следующее значение:
- Нет никаких последствий, что другие потоки или псевдонимы не имеют доступа на
xзапись. - Не существует никаких последствий, которые неизменяемы в силу того,
SomeStructxчто они являются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#
C# поддерживает in ref ключевые слова и out ref ключевые слова, помимо ref возвращаемого значения. В следующей таблице показано, как F# интерпретирует то, что C# выдает:
| Конструкция C# | F# infers |
|---|---|
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 в аргументе или возврате |
Аргумент 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-like
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. Оба должны присутствовать в типе.
Структуру типа "byrefкак" в F# — это тип значения, привязанного к стеку. Он никогда не выделяется в управляемой куче. Например byref, структуру полезно для высокопроизводительного программирования, так как она применяется с набором надежных проверок времени существования и без отслеживания. Ниже приведены правила.
- Их можно использовать в качестве параметров функции, параметров метода, локальных переменных, возвращаемого методом.
- Они не могут быть статическими или экземплярами элементов класса или обычной структуры.
- Они не могут быть захвачены какой-либо конструкцией закрытия (
asyncметодами или лямбда-выражениями). - Их нельзя использовать в качестве универсального параметра.
- Начиная с F# 9, это ограничение ослабляется, если универсальный параметр определен в C# с помощью средства защиты от ссылок. F# может создавать экземпляры таких универсальных типов в типах и методах с помощью таких типов, как и типы. В нескольких примерах это влияет на типы делегатов BCL (, ), интерфейсы (
Action<>IEnumerable<>,IComparable<>) и универсальные аргументы с помощью функции накопительного устройства (String.string Create<TState>(int length, TState state, SpanAction<char, TState> action)).Func<> - Невозможно создать универсальный код, поддерживающий типы byref-like в F#.
- Начиная с F# 9, это ограничение ослабляется, если универсальный параметр определен в C# с помощью средства защиты от ссылок. F# может создавать экземпляры таких универсальных типов в типах и методах с помощью таких типов, как и типы. В нескольких примерах это влияет на типы делегатов BCL (, ), интерфейсы (
Эта последняя точка имеет решающее значение для программирования в стиле конвейера F#, так как |> это универсальная функция, которая параметризирует типы входных данных. Это ограничение может быть расслаблено |> в будущем, так как оно является встроенным и не делает никаких вызовов нелинейных универсальных функций в своем теле.
Хотя эти правила строго ограничивают использование, они делают это для выполнения обещания высокопроизводительных вычислений в безопасном режиме.
Возвращает byref
Байтовые возвраты из функций или членов F# могут быть созданы и использованы. При использовании byrefметода -returning значение неявно различается. Рассмотрим пример.
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
Определение области для byrefs
Значение 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!
()
Это предотвращает получение различных результатов в зависимости от того, компилируется ли оптимизация или нет.