Новые возможности F# 6
В F# 6 добавлено несколько улучшений языка F # и F# Interactive. Она выпущена с .NET 6.
Вы можете скачать последний пакет SDK для .NET на странице скачиваемых файлов .NET.
Начать
F# 6 доступен во всех дистрибутивах .NET Core и инструментах Visual Studio. Дополнительные сведения см. в статье "Начало работы с F#".
задача {...}
F# 6 включает встроенную поддержку разработки задач .NET в коде F#. Например, рассмотрим следующий код 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:3566
), чтобы начать отчеты об 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])
or
Tensor.Create([int64 100; int64 10; int64 10])
В F# 6 расширение выполняется автоматически для int32
, int32
в nativeint
int32
double
том случае, если int64
исходный и целевой тип известны во время вывода типов. Поэтому в таких случаях, как предыдущие примеры, int32
можно использовать литералы:
Tensor.Create([100; 10; 10])
Несмотря на это изменение, F# продолжает использовать явное расширение числовых типов в большинстве случаев. Например, неявное расширение не применяется к другим числовым типам, таким как int8
или int16
, или изfloat64
float32
, или когда исходный или целевой тип неизвестен. Вы также можете включить предупреждение для отображения предупреждения /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:3391
/warnaserror:3389
и /warnaserror:3395
.
Форматирование двоичных чисел
F# 6 добавляет %B
шаблон в доступные описатели формата для форматов двоичных чисел. Рассмотрим следующий код F#:
printf "%o" 123
printf "%B" 123
Этот код выводит следующие выходные данные:
173
1111011
Эта функция реализует F# RFC FS-1100.
Dis карта s on use bindings
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
- List/Array/Seq.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.nullPtr
NativePtr.isNullPtr
NativePtr.initBlock
NativePtr.clear
NativePtr.copy
NativePtr.copyBlock
NativePtr.ofILSigPtr
NativePtr.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# Interactive". Установите для параметра "Использовать скрипты.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
,lsr
lxor
lsl
илиasr
в качестве операторов infix. Это infix ключевое слово в F#, так как они были infix ключевое слово в OCaml и не определены в FSharp.Core. Теперь при использовании этих ключевое слово появится предупреждение.
Это реализует F# RFC FS-1114.