Новые возможности 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.

Это изменение дает три преимущества:

  1. Требуется меньше явных переадресов
  2. Требуется меньше явных целочисленных преобразований
  3. Поддержка первого класса для . Добавлены неявные преобразования в стиле 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 в nativeintint32doubleтом случае, если int64исходный и целевой тип известны во время вывода типов. Поэтому в таких случаях, как предыдущие примеры, int32 можно использовать литералы:

Tensor.Create([100; 10; 10])

Несмотря на это изменение, F# продолжает использовать явное расширение числовых типов в большинстве случаев. Например, неявное расширение не применяется к другим числовым типам, таким как int8 или int16, или изfloat64float32, или когда исходный или целевой тип неизвестен. Вы также можете включить предупреждение для отображения предупреждения /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, lsrlxorlslили asr в качестве операторов infix. Это infix ключевое слово в F#, так как они были infix ключевое слово в OCaml и не определены в FSharp.Core. Теперь при использовании этих ключевое слово появится предупреждение.

Это реализует F# RFC FS-1114.