Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
F# 6 добавляет несколько улучшений в язык F# и F# Interactive. Она выпущена с .NET 6.
Скачать последний пакет SDK для .NET можно на странице загрузки .NET.
Начало работы
F# 6 доступен во всех дистрибутивах .NET Core и инструментах Visual Studio. Дополнительные сведения см. в статье "Начало работы с F#".
задача {...}
F# 6 включает встроенную поддержку разработки задач .NET в коде F# с помощью выражений задач. Выражения задач похожи на асинхронные выражения, но позволяют создавать задачи .NET напрямую.
Например, рассмотрим следующий код F# для создания задачи, совместимой с .NET.
let readFilesTask (path1, path2) =
async {
let! bytes1 = File.ReadAllBytesAsync(path1) |> Async.AwaitTask
let! bytes2 = File.ReadAllBytesAsync(path2) |> Async.AwaitTask
return Array.append bytes1 bytes2
} |> Async.StartAsTask
С помощью F# 6 этот код можно переписать следующим образом.
let readFilesTask (path1, path2) =
task {
let! bytes1 = File.ReadAllBytesAsync(path1)
let! bytes2 = File.ReadAllBytesAsync(path2)
return Array.append bytes1 bytes2
}
Поддержка задач была доступна для F# 5 с помощью отличных библиотек TaskBuilder.fs и Ply. Необходимо просто перенести код в встроенную поддержку. Однако существуют некоторые различия: пространства имен и вывод типов немного отличаются между встроенной поддержкой и этими библиотеками, и могут понадобиться некоторые дополнительные аннотации типов. При необходимости эти библиотеки сообщества по-прежнему можно использовать с F# 6, если вы ссылаетесь на них явно и открываете правильные пространства имен в каждом файле.
Использование task {…} очень похоже на использование async {…}. Использование task {…} имеет несколько преимуществ по сравнению async {…}с:
- Затраты на выполнение
task {...}ниже, что, возможно, улучшает производительность в активно используемых участках кода, где асинхронная работа выполняется быстро. - Улучшены отладочные шаги и трассировки стека для
task {…}. - Взаимодействие с пакетами .NET, которые ожидают или создают задачи, проще.
Если вы знакомы с async {…}, есть некоторые отличия, которые следует учитывать:
-
task {…}немедленно выполняет задачу до первой точки ожидания. -
task {…}не распространяет маркер отмены неявно. -
task {…}не выполняет неявных проверок отмены. -
task {…}не поддерживает асинхронные хвостовые вызовы. Это означает, что использованиеreturn! ..рекурсивно может привести к переполнению стека, если нет промежуточных асинхронных ожиданий.
Как правило, следует рассмотреть возможность использования task {…} вместо async {…} в новом коде, если вы работаете с библиотеками .NET, которые используют задачи, и если вы не полагаетесь на асинхронные вызовы хвостовых функций или неявную передачу маркера отмены. В существующем коде следует переключаться на task {…} только после того, как вы проверите свой код, чтобы убедиться в том, что вы не полагаетесь на ранее упомянутые характеристики async {…}.
Эта функция реализует F# RFC FS-1097.
Более простой синтаксис индексирования с помощью expr[idx]
F# 6 позволяет использовать синтаксис expr[idx] для индексирования и срезов коллекций.
До и включая F# 5, F# использовал expr.[idx] в качестве синтаксиса индексирования. Разрешение на использование expr[idx] основано на повторяющихся отзывах от тех, кто изучает F# или впервые сталкивается с F#, что использование точечной нотации рассматривается как ненужное отклонение от стандартной отраслевой практики.
Это не критическое изменение, так как по умолчанию предупреждения не создаются при использовании expr.[idx]. Однако некоторые информационные сообщения, предлагающие уточнения кода, выдаются. Кроме того, можно активировать дополнительные информационные сообщения. Например, можно активировать необязательное информационное предупреждение (/warnon:3366), чтобы начать сообщать об использовании нотации expr.[idx]. Дополнительные сведения см. в нотации индексатора.
В новом коде рекомендуется систематическое использование expr[idx] в качестве синтаксиса индексирования.
Эта функция реализует F# RFC FS-1110.
Представления структуры для частичных активных шаблонов
F# 6 расширяет функцию "активные шаблоны" с необязательными представлениями структуры для частичных активных шаблонов. Это позволяет использовать атрибут для лимитации частично активного шаблона с целью возвращения опции значения.
[<return: Struct>]
let (|Int|_|) str =
match System.Int32.TryParse(str) with
| true, int -> ValueSome(int)
| _ -> ValueNone
Требуется использование атрибута. На сайтах использования код не изменяется. Чистый результат заключается в том, что выделение уменьшается.
Эта функция реализует F# RFC FS-1039.
Перегруженные пользовательские операции в выражениях вычислений
F# 6 позволяет использовать CustomOperationAttribute в перегруженных методах.
Рассмотрим следующее использование построителя contentвыражений вычислений:
let mem = new System.IO.MemoryStream("Stream"B)
let content = ContentBuilder()
let ceResult =
content {
body "Name"
body (ArraySegment<_>("Email"B, 0, 5))
body "Password"B 2 4
body "BYTES"B
body mem
body "Description" "of" "content"
}
body Здесь пользовательская операция принимает разное количество аргументов разных типов. Это поддерживается реализацией следующего построителя, использующего перегрузку:
type Content = ArraySegment<byte> list
type ContentBuilder() =
member _.Run(c: Content) =
let crlf = "\r\n"B
[|for part in List.rev c do
yield! part.Array[part.Offset..(part.Count+part.Offset-1)]
yield! crlf |]
member _.Yield(_) = []
[<CustomOperation("body")>]
member _.Body(c: Content, segment: ArraySegment<byte>) =
segment::c
[<CustomOperation("body")>]
member _.Body(c: Content, bytes: byte[]) =
ArraySegment<byte>(bytes, 0, bytes.Length)::c
[<CustomOperation("body")>]
member _.Body(c: Content, bytes: byte[], offset, count) =
ArraySegment<byte>(bytes, offset, count)::c
[<CustomOperation("body")>]
member _.Body(c: Content, content: System.IO.Stream) =
let mem = new System.IO.MemoryStream()
content.CopyTo(mem)
let bytes = mem.ToArray()
ArraySegment<byte>(bytes, 0, bytes.Length)::c
[<CustomOperation("body")>]
member _.Body(c: Content, [<ParamArray>] contents: string[]) =
List.rev [for c in contents -> let b = Text.Encoding.ASCII.GetBytes c in ArraySegment<_>(b,0,b.Length)] @ c
Эта функция реализует F# RFC FS-1056.
Шаблоны "как"
В F# 6 правая сторона as шаблона теперь сама может быть шаблоном. Это важно, если тест типа дал более строгий тип входным данным. Например, рассмотрим следующий код:
type Pair = Pair of int * int
let analyzeObject (input: obj) =
match input with
| :? (int * int) as (x, y) -> printfn $"A tuple: {x}, {y}"
| :? Pair as Pair (x, y) -> printfn $"A DU: {x}, {y}"
| _ -> printfn "Nope"
let input = box (1, 2)
Во всех случаях входной объект проверяется на тип. Правая сторона as шаблона теперь разрешена быть дополнительным шаблоном, который сам по себе может соответствовать объекту с более строгим типом.
Эта функция реализует F# RFC FS-1105.
Правки синтаксиса отступов
F# 6 удаляет ряд несоответствий и ограничений при использовании синтаксиса, чувствительного к отступам. См. RFC FS-1108. Это устраняет 10 существенных проблем, выделенных пользователями F# с версии 4.0.
Например, в F# 5 разрешен следующий код:
let c = (
printfn "aaaa"
printfn "bbbb"
)
Однако следующий код не разрешен (он создал предупреждение):
let c = [
1
2
]
В F# 6 оба разрешены. Это делает F# более простым и легким для изучения. Участник сообщества F# Хадриан Тан возглавил эту работу, включая замечательное и весьма ценное систематическое тестирование функции.
Эта функция реализует F# RFC FS-1108.
Дополнительные неявные преобразования
В F# 6 мы активировали поддержку дополнительных "имплицитных" и "управляемых типом" преобразований, как описано в RFC FS-1093.
Это изменение дает три преимущества:
- Требуется меньше явных переадресов
- Требуется меньше явных целочисленных преобразований
- Добавлена полноценная поддержка неявных преобразований в стиле .NET.
Эта функция реализует F# RFC FS-1093.
Дополнительные неявные преобразования к базовому типу
F# 6 реализует дополнительные неявные преобразования передачи. Например, в F# 5 и более ранних версиях были необходимы восходящие преобразования для выражения возврата при реализации функции, в которой выражения имели разные подтипы в разных ветвях, даже при наличии аннотации типа. Рассмотрим следующий код F# 5:
open System
open System.IO
let findInputSource () : TextReader =
if DateTime.Now.DayOfWeek = DayOfWeek.Monday then
// On Monday a TextReader
Console.In
else
// On other days a StreamReader
File.OpenText("path.txt") :> TextReader
Здесь ветви условного вычисления вычисляют TextReader и StreamReader соответственно, а поднятие типа добавлено для того, чтобы обе ветви имели тип StreamReader. В F# 6 эти upcasts теперь добавляются автоматически. Это означает, что код проще:
let findInputSource () : TextReader =
if DateTime.Now.DayOfWeek = DayOfWeek.Monday then
// On Monday a TextReader
Console.In
else
// On other days a StreamReader
File.OpenText("path.txt")
По желанию можно включить предупреждение /warnon:3388, чтобы показывать предупреждение в каждой точке, где используется дополнительное неявное расширение, как описано в дополнительных предупреждениях для неявных преобразований.
Неявные преобразования целых чисел
В F# 6 32-разрядные целые числа расширяются до 64-разрядных целых чисел, если оба типа известны. Например, рассмотрим типичную форму API:
type Tensor(…) =
static member Create(sizes: seq<int64>) = Tensor(…)
В F# 5 целые литералы для int64 должны использоваться:
Tensor.Create([100L; 10L; 10L])
или
Tensor.Create([int64 100; int64 10; int64 10])
В F# 6 расширение выполняется автоматически для int32 к int64, int32 к nativeint, и int32 к double, когда исходный и целевой тип известны во время вывода типов. Поэтому в таких случаях, как предыдущие примеры, int32 можно использовать литералы:
Tensor.Create([100; 10; 10])
Несмотря на это изменение, F# продолжает использовать явное расширение числовых типов в большинстве случаев. Например, неявное расширение не применяется к другим числовым типам, таким как int8 или int16, или изfloat32float64, или когда исходный или целевой тип неизвестен. Вы также можете опционально включить предупреждение /warnon:3389 для отображения сообщения в каждой точке, где используется неявное числовое расширение типа, как описано в дополнительных предупреждениях для неявных преобразований.
Высококачественная поддержка неявных преобразований в стиле .NET
В F# 6 преобразования .NET "op_Implicit" применяются автоматически в коде F# при вызове методов. Например, в F# 5 необходимо было использовать XName.op_Implicit при работе с API .NET для XML:
open System.Xml.Linq
let purchaseOrder = XElement.Load("PurchaseOrder.xml")
let partNos = purchaseOrder.Descendants(XName.op_Implicit "Item")
В F# 6 op_Implicit преобразования автоматически применяются к выражениям аргументов, когда доступны типы исходного и целевого выражения.
open System.Xml.Linq
let purchaseOrder = XElement.Load("PurchaseOrder.xml")
let partNos = purchaseOrder.Descendants("Item")
При необходимости можно включить предупреждение /warnon:3395, чтобы показать предупреждение во всех случаях op_Implicit, где аргументам метода используются расширяющие преобразования, как описано в Необязательные предупреждения для неявных преобразований.
Замечание
В первом выпуске F# 6 этот номер предупреждения был /warnon:3390. Из-за конфликта номер предупреждения был обновлен до /warnon:3395.
Опциональные предупреждения для неявных преобразований
Типо-ориентированные и неявные преобразования могут плохо сочетаться с выводом типов и усложнить понимание кода. По этой причине некоторые средства устранения рисков существуют для обеспечения того, чтобы эта функция не злоупотреблялась в коде F#. Во-первых, как исходный, так и конечный тип должны быть строго известны, без неоднозначности или дополнительного вывода типов. Во-вторых, предупреждения о согласии можно активировать, чтобы сообщить об использовании неявных преобразований с одним предупреждением по умолчанию:
-
/warnon:3388(дополнительная неявная рассылка) -
/warnon:3389(неявное расширение числовых элементов) -
/warnon:3391(op_Implicit в неметодических аргументах, по умолчанию) -
/warnon:3395(op_Implicit в аргументах метода)
Если ваша команда хочет запретить все использование неявных преобразований, можно также указать /warnaserror:3388, /warnaserror:3389/warnaserror:3391и /warnaserror:3395.
Форматирование двоичных чисел
F# 6 добавляет %B шаблон в доступные описатели формата для форматов двоичных чисел. Рассмотрим следующий код F#:
printf "%o" 123
printf "%B" 123
Этот код выводит следующие выходные данные:
173
1111011
Эта функция реализует F# RFC FS-1100.
Отменяет использование привязок
F# 6 позволяет _ использовать в привязке use , например:
let doSomething () =
use _ = System.IO.File.OpenText("input.txt")
printfn "reading the file"
Эта функция реализует F# RFC FS-1102.
InlineIfLambda
Компилятор F# включает оптимизатор, выполняющий встраивание кода. В F# 6 мы добавили новую декларативную возможность, которая позволяет коду при необходимости указывать, что, если аргумент определяется как лямбда-функция, этот аргумент должен всегда встраиваться в местах вызова.
Например, рассмотрим следующую iterateTwice функцию для обхода массива:
let inline iterateTwice ([<InlineIfLambda>] action) (array: 'T[]) =
for j = 0 to array.Length-1 do
action array[j]
for j = 0 to array.Length-1 do
action array[j]
Если место вызова:
let arr = [| 1.. 100 |]
let mutable sum = 0
arr |> iterateTwice (fun x ->
sum <- sum + x)
Затем после встраивания и других оптимизаций код становится следующим:
let arr = [| 1.. 100 |]
let mutable sum = 0
for j = 0 to arr.Length-1 do
sum <- sum + arr[j]
for j = 0 to arr.Length-1 do
sum <- sum + arr[j]
В отличие от предыдущих версий F#, эта оптимизация применяется независимо от размера лямбда-выражения. Эту функцию также можно использовать для реализации развёртки циклов и аналогичных преобразований более надёжным образом.
Предупреждение о согласии (/warnon:3517отключено по умолчанию) можно включить, чтобы указать места в коде, где InlineIfLambda аргументы не привязаны к лямбда-выражениям на сайтах вызовов. В обычных ситуациях это предупреждение не должно быть включено. Однако в некоторых типах высокопроизводительного программирования может быть полезно убедиться, что весь код встраивается и уплощён.
Эта функция реализует F# RFC FS-1098.
Возобновляемый код
Поддержка task {…} F# 6 основана на основе повторного кодаRFC FS-1087. Возобновляемый код — это техническая функция, которая может использоваться для создания множества высокопроизводительных асинхронных и приостанавливаемых состояний машин.
Дополнительные функции коллекции
FSharp.Core 6.0.0 добавляет пять новых операций в основные функции коллекции. Ниже приведены следующие функции:
- List/Array/Seq.insertAt
- List/Array/Seq.removeAt
- Список/Массив/Последовательность.updateAt
- List/Array/Seq.insertManyAt
- List/Array/Seq.removeManyAt
Эти функции выполняют операции копирования и обновления для соответствующего типа коллекции или последовательности. Этот тип операции представляет собой форму функционального обновления. Примеры использования этих функций см. в соответствующей документации, например List.insertAt.
Например, рассмотрим модель, сообщение и логику обновления для простого приложения Todo List, написанного в стиле Elmish. Здесь пользователь взаимодействует с приложением, создает сообщения и update обрабатывает эти сообщения, создавая новую модель:
type Model =
{ ToDo: string list }
type Message =
| InsertToDo of index: int * what: string
| RemoveToDo of index: int
| LoadedToDos of index: int * what: string list
let update (model: Model) (message: Message) =
match message with
| InsertToDo (index, what) ->
{ model with ToDo = model.ToDo |> List.insertAt index what }
| RemoveToDo index ->
{ model with ToDo = model.ToDo |> List.removeAt index }
| LoadedToDos (index, what) ->
{ model with ToDo = model.ToDo |> List.insertManyAt index what }
С этими новыми функциями логика понятна и проста и зависит только от неизменяемых данных.
Эта функция реализует F# RFC FS-1113.
Карта содержит ключи и значения
В FSharp.Core 6.0.0 тип Map теперь поддерживает свойства "Ключи и значения ". Эти свойства не копируют базовую коллекцию.
Эта функция описана в F# RFC FS-1113.
Дополнительные встроенные компоненты для NativePtr
FSharp.Core 6.0.0 добавляет новые встроенные компоненты в модуль NativePtr :
NativePtr.nullPtrNativePtr.isNullPtrNativePtr.initBlockNativePtr.clearNativePtr.copyNativePtr.copyBlockNativePtr.ofILSigPtrNativePtr.toILSigPtr
Как и в других функциях, эти функции в NativePtr встраиваются, и их использование выдает предупреждения, если /nowarn:9 не используется. Использование этих функций ограничивается неуправляемыми типами.
Эта функция описана в F# RFC FS-1109.
Дополнительные числовые типы с аннотациями единиц
В F# 6 следующие типы или псевдонимы сокращенного типа теперь поддерживают аннотации единиц измерения. Новые дополнения отображаются полужирным шрифтом:
| F# псевдоним | Тип CLR |
|---|---|
float32/single |
System.Single |
float/double |
System.Double |
decimal |
System.Decimal |
sbyte/int8 |
System.SByte |
int16 |
System.Int16 |
int/int32 |
System.Int32 |
int64 |
System.Int64 |
byte/uint8 |
System.Byte |
uint16 |
System.UInt16 |
uint/uint32 |
System.UInt32 |
uint64 |
System.UIn64 |
nativeint |
System.IntPtr |
unativeint |
System.UIntPtr |
Например, можно аннотировать целое число без знака следующим образом:
[<Measure>]
type days
let better_age = 3u<days>
Эта функция описана в F# RFC FS-1091.
Информационные предупреждения для редко используемых символьных операторов
F# 6 добавляет мягкое руководство, которое денормализует использование :=, !, incr и decr в F# 6 и более. С использованием этих операторов и функций создаются информационные сообщения, которые предлагают заменить код явным использованием свойства Value.
В программировании на F# ссылочные ячейки можно использовать для изменяемых регистров, размещённых в куче. Хотя они иногда полезны, в современном кодировании на F# они редко используются, потому что вместо этого можно использовать let mutable. Базовая библиотека F# включает два оператора := и ! две функции incr , а decr также связанные с вызовами ссылок. Наличие этих операторов делает ссылочные ячейки более центральными для программирования F#, чем они должны быть, требуя, чтобы все программисты F# знали эти операторы. Кроме того, ! оператор можно легко путать с not операцией на C# и других языках, потенциально тонким источником ошибок при переводе кода.
Обоснование этого изменения заключается в сокращении числа операторов, которые программист F# должен знать, и таким образом упростить F# для начинающих.
Например, рассмотрим следующий код F# 5:
let r = ref 0
let doSomething() =
printfn "doing something"
r := !r + 1
Во-первых, ссылочные ячейки редко требуются в современном кодировании F#, так как let mutable обычно можно использовать вместо этого:
let mutable r = 0
let doSomething() =
printfn "doing something"
r <- r + 1
При использовании ссылочных ячеек F# 6 выдает информационное предупреждение с просьбой изменить последнюю строку на r.Value <- r.Value + 1, и предоставляя ссылку на дополнительные рекомендации по соответствующему использованию ссылочных ячеек.
let r = ref 0
let doSomething() =
printfn "doing something"
r.Value <- r.Value + 1
Эти сообщения не являются предупреждениями; они представляют собой информационные сообщения, отображаемые в выходных данных интегрированной среды разработки и компилятора. F# сохраняет обратную совместимость.
Эта функция реализует F# RFC FS-1111.
Инструменты F#: .NET 6 по умолчанию для сценариев в Visual Studio
Если вы открываете или выполняете скрипт F# (.fsx) в Visual Studio, по умолчанию скрипт будет анализироваться и выполняться с помощью .NET 6 с 64-разрядным выполнением. Эта функция была в предварительной версии в более поздних выпусках Visual Studio 2019 и теперь включена по умолчанию.
Чтобы включить скрипты .NET Framework, выберите Инструменты>Параметры>Инструменты F#>F# Интерактивный. Установите для параметра "Использовать скрипты .NET Core" значение false, а затем перезапустите интерактивное окно F#. Этот параметр влияет как на редактирование скрипта, так и на выполнение скрипта. Чтобы включить 32-разрядное выполнение скриптов .NET Framework, также установите 64-разрядный интерактивный F# на значение false. Для скриптов .NET Core нет 32-разрядного варианта.
Инструментарий F#: зафиксируйте версию SDK для ваших F# скриптов
Если вы выполняете скрипт с помощью dotnet fsi каталога, содержащего файлglobal.json с параметром пакета SDK для .NET, то для выполнения и редактирования скрипта будет использоваться указанная версия пакета SDK для .NET. Эта функция доступна в более поздних версиях F# 5.
Например, предположим, что в каталоге есть скрипт со следующим global.json файлом, указывающим политику версии пакета SDK для .NET:
{
"sdk": {
"version": "5.0.200",
"rollForward": "minor"
}
}
Если вы сейчас выполните скрипт с использованием dotnet fsi, из этого каталога будет учитываться версия SDK. Это мощная функция, которая позволяет "заблокировать" пакет SDK, используемый для компиляции, анализа и выполнения скриптов.
Если вы открываете и редактируете скрипт в Visual Studio и других средах разработки, средство будет учитывать этот параметр при анализе и проверке скрипта. Если пакет SDK не найден, его необходимо установить на компьютере разработки.
В Linux и других системах Unix вы можете объединить это с шебангом , чтобы также указать языковую версию для прямого выполнения скрипта. Простой шебанг для script.fsx :
#!/usr/bin/env -S dotnet fsi
printfn "Hello, world"
Теперь скрипт можно выполнять напрямую с помощью script.fsx. Это можно объединить с определенной версией языка, отличной от по умолчанию, следующим образом:
#!/usr/bin/env -S dotnet fsi --langversion:5.0
Замечание
Этот параметр игнорируется средствами редактирования, которые будут анализировать скрипт при условии последней версии языка.
Удаление устаревших функций
С момента F# 2.0 некоторые устаревшие функции уже давно выдают предупреждения. Использование этих функций в F# 6 дает ошибки, если вы явно не используете /langversion:5.0. Ниже приведены функции, которые дают ошибки:
- Например, несколько универсальных параметров с использованием имени
(int, int) Dictionaryтипа postfix. Это становится ошибкой в F# 6. Вместо этого следует использовать стандартный синтаксисDictionary<int,int>. -
#indent "off". Это становится ошибкой. -
x.(expr). Это становится ошибкой. -
module M = struct … end. Это становится ошибкой. - Использование входных данных
*.mlи*.mli. Это становится ошибкой. - Использование
(*IF-CAML*)или(*IF-OCAML*). Это становится ошибкой. - Использование операторов
land, ,lor,lxorlsllsrилиasrв качестве операторов infix. Это инфиксные ключевые слова в F#, поскольку они были инфиксными ключевыми словами в OCaml и не определены в FSharp.Core. Использование этих ключевых слов теперь вызывает предупреждение.
Это реализует F# RFC FS-1114.