Partilhar via


O que há de novo no F# 6

O F# 6 adiciona várias melhorias à linguagem F# e ao F# interativo. É lançado com o .NET 6.

Você pode baixar o SDK mais recente do .NET na página de downloads do .NET.

Começar agora

F# 6 está disponível em todas as distribuições .NET Core e ferramentas do Visual Studio. Para obter mais informações, consulte Introdução ao F#.

tarefa {...}

O F# 6 inclui suporte nativo para a criação de tarefas .NET em código F#. Por exemplo, considere o seguinte código F# para criar um arquivo . Tarefa compatível com 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

Usando F# 6, esse código pode ser reescrito da seguinte maneira.

let readFilesTask (path1, path2) =
   task {
        let! bytes1 = File.ReadAllBytesAsync(path1)
        let! bytes2 = File.ReadAllBytesAsync(path2)
        return Array.append bytes1 bytes2
   }

O suporte a tarefas estava disponível para F# 5 através das excelentes bibliotecas TaskBuilder.fs e Ply. Deve ser simples migrar o código para o suporte interno. No entanto, há algumas diferenças: namespaces e inferência de tipo diferem ligeiramente entre o suporte interno e essas bibliotecas, e algumas anotações de tipo adicionais podem ser necessárias. Se necessário, você ainda pode usar essas bibliotecas de comunidade com F# 6 se fizer referência a elas explicitamente e abrir os namespaces corretos em cada arquivo.

Usar task {…} é muito semelhante a usar async {…}. A utilização task {…} tem várias vantagens sobre async {…}:

  • A sobrecarga do é menor, possivelmente melhorando o desempenho em caminhos de task {...} código ativo onde o trabalho assíncrono é executado rapidamente.
  • Depurar a revisão e os rastreamentos de pilha para task {…} é melhor.
  • Interoperar com pacotes .NET que esperam ou produzem tarefas é mais fácil.

Se você está familiarizado com async {…}o , há algumas diferenças a serem observadas:

  • task {…} executa imediatamente a tarefa até o primeiro ponto de espera.
  • task {…} não propaga implicitamente um token de cancelamento.
  • task {…} não efetua verificações de cancelamento implícitas.
  • task {…} não suporta tailcalls assíncronas. Isso significa que o uso return! .. recursivo pode resultar em estouros de pilha se não houver esperas assíncronas intervenientes.

Em geral, você deve considerar o uso task {…} em async {…} novo código se estiver interoperando com bibliotecas .NET que usam tarefas e se não depender de tailcalls de código assíncronas ou propagação de token de cancelamento implícito. No código existente, você só deve alternar para task {…} depois de revisar seu código para garantir que não está confiando nas características mencionadas anteriormente do async {…}.

Este recurso implementa F # RFC FS-1097.

Sintaxe de indexação mais simples com expr[idx]

F# 6 permite a sintaxe expr[idx] para indexação e fatiamento de coleções.

Até e incluindo F# 5, F# tem usado expr.[idx] como sintaxe de indexação. Permitir o uso de é baseado em feedback repetido de quem está aprendendo F# ou vendo F# pela primeira vez que o uso de indexação de notação de expr[idx] pontos aparece como uma divergência desnecessária da prática padrão do setor.

Esta não é uma alteração de quebra porque, por padrão, nenhum aviso é emitido sobre o uso do expr.[idx]. No entanto, algumas mensagens informativas que sugerem esclarecimentos de código são emitidas. Opcionalmente, você também pode ativar outras mensagens informativas. Por exemplo, você pode ativar um aviso informativo opcional (/warnon:3566) para começar a relatar usos da expr.[idx] notação. Para obter mais informações, consulte Notação do indexador.

Em novo código, recomendamos o uso sistemático de como sintaxe de expr[idx] indexação.

Este recurso implementa F # RFC FS-1110.

Estruturar representações para padrões ativos parciais

O F# 6 aumenta o recurso "padrões ativos" com representações struct opcionais para padrões ativos parciais. Isso permite que você use um atributo para restringir um padrão ativo parcial para retornar uma opção de valor:

[<return: Struct>]
let (|Int|_|) str =
   match System.Int32.TryParse(str) with
   | true, int -> ValueSome(int)
   | _ -> ValueNone

O uso do atributo é obrigatório. Em sites de uso, o código não muda. O resultado líquido é a redução das dotações.

Este recurso implementa F # RFC FS-1039.

Operações personalizadas sobrecarregadas em expressões computacionais

F# 6 permite que você use CustomOperationAttribute nos métodos sobrecarregados.

Considere o seguinte uso de um construtor contentde expressões computacionais:

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"
    }

Aqui, a body operação personalizada usa um número variável de argumentos de diferentes tipos. Isso é suportado pela implementação do seguinte construtor, que usa sobrecarga:

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

Este recurso implementa F # RFC FS-1056.

Padrões "como"

Em F# 6, o lado direito de um as padrão agora pode ser um padrão. Isso é importante quando um teste de tipo deu um tipo mais forte a uma entrada. Por exemplo, considere o seguinte código:

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)

Em cada caso de padrão, o objeto de entrada é testado por tipo. O lado direito do as padrão agora pode ser um padrão adicional, que pode corresponder ao objeto no tipo mais forte.

Este recurso implementa F # RFC FS-1105.

Revisões da sintaxe de recuo

F# 6 remove uma série de inconsistências e limitações em seu uso de sintaxe com reconhecimento de recuo. Consulte RFC FS-1108. Isso resolve 10 problemas significativos destacados pelos usuários do F# desde o F# 4.0.

Por exemplo, em F# 5 o seguinte código foi permitido:

let c = (
    printfn "aaaa"
    printfn "bbbb"
)

No entanto, o seguinte código não foi permitido (produziu um aviso):

let c = [
    1
    2
]

Em F# 6, ambos são permitidos. Isso torna o F# mais simples e fácil de aprender. O contribuidor da comunidade F# Hadrian Tang liderou o caminho nisso, incluindo testes sistemáticos notáveis e altamente valiosos do recurso.

Este recurso implementa F # RFC FS-1108.

Conversões implícitas adicionais

No F# 6, ativamos o suporte para conversões adicionais "implícitas" e "direcionadas por tipo", conforme descrito no RFC FS-1093.

Esta mudança traz três vantagens:

  1. São necessários menos upcasts explícitos
  2. São necessárias menos conversões inteiras explícitas
  3. Suporte de primeira classe para . Conversões implícitas no estilo NET são adicionadas

Este recurso implementa F # RFC FS-1093.

Conversões adicionais implícitas de upcast

O F# 6 implementa conversões de upcast implícitas adicionais. Por exemplo, em F# 5 e versões anteriores, upcasts eram necessários para a expressão de retorno ao implementar uma função onde as expressões tinham subtipos diferentes em ramificações diferentes, mesmo quando uma anotação de tipo estava presente. Considere o seguinte código 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

Aqui, as ramificações do cálculo condicional a TextReader e StreamReader respectivamente, e o upcast foram adicionados para fazer com que ambas as ramificações tenham o tipo StreamReader. No F# 6, esses upcasts agora são adicionados automaticamente. Isto significa que o código é mais simples:

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")

Opcionalmente, você pode habilitar o aviso /warnon:3388 para mostrar um aviso em cada ponto em que um upcast implícito adicional é usado, conforme descrito em Avisos opcionais para conversões implícitas.

Conversões de inteiros implícitas

No F# 6, inteiros de 32 bits são ampliados para inteiros de 64 bits quando ambos os tipos são conhecidos. Por exemplo, considere uma forma típica de API:

type Tensor(…) =
    static member Create(sizes: seq<int64>) = Tensor(…)

Em F# 5, literais inteiros para int64 devem ser usados:

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

ou

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

Em F# 6, a ampliação acontece automaticamente para int32 , para nativeint, e int32 para double, quando os tipos de origem e destino são conhecidos durante a int64int32 inferência de tipo. Assim, em casos como os exemplos anteriores, int32 literais podem ser usados:

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

Apesar dessa mudança, o F# continua a usar o alargamento explícito de tipos numéricos na maioria dos casos. Por exemplo, o alargamento implícito não se aplica a outros tipos numéricos, como int8 ou int16, ou de para , ou quando qualquer float64tipo de float32 origem ou destino é desconhecido. Opcionalmente, você também pode habilitar o aviso /warnon:3389 para mostrar um aviso em cada ponto em que o alargamento numérico implícito é usado, conforme descrito em Avisos opcionais para conversões implícitas.

Suporte de primeira classe para . Conversões implícitas no estilo NET

Em F# 6, as conversões "op_Implicit" do .NET são aplicadas automaticamente no código F# ao chamar métodos. Por exemplo, no F# 5 era necessário usar XName.op_Implicit ao trabalhar com APIs .NET para XML:

open System.Xml.Linq
let purchaseOrder = XElement.Load("PurchaseOrder.xml")
let partNos = purchaseOrder.Descendants(XName.op_Implicit "Item")

No F# 6, op_Implicit as conversões são aplicadas automaticamente para expressões de argumento quando os tipos estão disponíveis para expressão de origem e tipo de destino:

open System.Xml.Linq
let purchaseOrder = XElement.Load("PurchaseOrder.xml")
let partNos = purchaseOrder.Descendants("Item")

Opcionalmente, você pode habilitar o aviso /warnon:3395 para mostrar um aviso a cada ponto op_Implicit em que o alargamento de conversões é usado em argumentos de método, conforme descrito em Avisos opcionais para conversões implícitas.

Nota

Na primeira versão do F# 6, esse número de aviso era /warnon:3390. Devido a um conflito, o número de aviso foi posteriormente atualizado para /warnon:3395.

Avisos opcionais para conversões implícitas

Conversões implícitas e direcionadas a tipos podem interagir mal com a inferência de tipo e levar a um código mais difícil de entender. Por esse motivo, existem algumas atenuações para ajudar a garantir que esse recurso não seja abusado no código F#. Em primeiro lugar, tanto o tipo de origem como o de destino devem ser fortemente conhecidos, sem ambiguidade ou inferência de tipo adicional. Em segundo lugar, os avisos de aceitação podem ser ativados para relatar qualquer uso de conversões implícitas, com um aviso ativado por padrão:

  • /warnon:3388 (adicional implícito reformulado)
  • /warnon:3389 (alargamento numérico implícito)
  • /warnon:3391 (op_Implicit em argumentos não metodológicos, por padrão)
  • /warnon:3395 (op_Implicit em argumentos de método)

Se sua equipe quiser proibir todos os usos de conversões implícitas, você também pode especificar /warnaserror:3388, /warnaserror:3389, /warnaserror:3391, e /warnaserror:3395.

Formatação para números binários

F# 6 adiciona o %B padrão aos especificadores de formato disponíveis para formatos de números binários. Considere o seguinte código F#:

printf "%o" 123
printf "%B" 123

Este código imprime a seguinte saída:

173
1111011

Este recurso implementa F # RFC FS-1100.

Devoluções em ligações de utilização

F# 6 permite _ ser usado em uma use ligação, por exemplo:

let doSomething () =
    use _ = System.IO.File.OpenText("input.txt")
    printfn "reading the file"

Este recurso implementa F # RFC FS-1102.

InlineIfLambda

O compilador F# inclui um otimizador que executa o inlining de código. Em F# 6, adicionamos um novo recurso declarativo que permite que o código indique opcionalmente que, se um argumento for determinado como uma função lambda, esse argumento deve sempre estar embutido em sites de chamada.

Por exemplo, considere a seguinte iterateTwice função para atravessar uma matriz:

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]

Se o site de chamada for:

let arr = [| 1.. 100 |]
let mutable sum = 0
arr  |> iterateTwice (fun x ->
    sum <- sum + x)

Depois de inline e outras otimizações, o código se torna:

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]

Ao contrário das versões anteriores do F#, essa otimização é aplicada independentemente do tamanho da expressão lambda envolvida. Esse recurso também pode ser usado para implementar o desenrolamento de loops e transformações semelhantes de forma mais confiável.

Um aviso de aceitação (/warnon:3517, desativado por padrão) pode ser ativado para indicar locais em seu código onde InlineIfLambda os argumentos não estão vinculados a expressões lambda em sites de chamada. Em situações normais, este aviso não deve ser ativado. No entanto, em certos tipos de programação de alto desempenho, pode ser útil garantir que todo o código seja embutido e nivelado.

Este recurso implementa F # RFC FS-1098.

Código retomável

O task {…} suporte do F# 6 é construído sobre uma base chamada códigoretomável RFC FS-1087. O código retomável é um recurso técnico que pode ser usado para criar muitos tipos de máquinas de estado assíncronas e de rendimento de alto desempenho.

Funções de recolha adicionais

FSharp.Core 6.0.0 adiciona cinco novas operações às funções principais de coleta. Estas funções são:

  • List/Array/Seq.insertAt
  • List/Array/Seq.removeAt
  • List/Array/Seq.updateAt
  • List/Array/Seq.insertManyAt
  • List/Array/Seq.removeManyAt

Todas essas funções executam operações de cópia e atualização no tipo ou sequência de coleção correspondente. Este tipo de operação é uma forma de "atualização funcional". Para obter exemplos de uso dessas funções, consulte a documentação correspondente, por exemplo, List.insertAt.

Como exemplo, considere o modelo, a mensagem e a lógica de atualização para um aplicativo "Lista Todo" simples escrito no estilo Elmish. Aqui o usuário interage com o aplicativo, gerando mensagens, e a função processa update essas mensagens, produzindo um novo modelo:

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 }

Com estas novas funções, a lógica é clara e simples e depende apenas de dados imutáveis.

Este recurso implementa F # RFC FS-1113.

Mapa tem chaves e valores

Em FSharp.Core 6.0.0, o Map tipo agora suporta as propriedades Keys e Values . Essas propriedades não copiam a coleção subjacente.

Esse recurso está documentado em F# RFC FS-1113.

Intrínsecos adicionais para NativePtr

FSharp.Core 6.0.0 adiciona novos intrínsecos ao módulo NativePtr :

  • NativePtr.nullPtr
  • NativePtr.isNullPtr
  • NativePtr.initBlock
  • NativePtr.clear
  • NativePtr.copy
  • NativePtr.copyBlock
  • NativePtr.ofILSigPtr
  • NativePtr.toILSigPtr

Tal como acontece com outras funções no NativePtr, estas funções estão embutidas, e a sua utilização emite avisos, a menos que /nowarn:9 seja utilizada. O uso dessas funções é restrito a tipos não gerenciados.

Esse recurso está documentado em F# RFC FS-1109.

Tipos numéricos adicionais com anotações de unidade

No F# 6, os seguintes tipos ou aliases de abreviação de tipo agora suportam anotações de unidade de medida. As novas adições são apresentadas a negrito:

Alias F# Tipo 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

Por exemplo, você pode anotar um inteiro não assinado da seguinte maneira:

[<Measure>]
type days

let better_age = 3u<days>

Esse recurso está documentado em F# RFC FS-1091.

Avisos informativos para operadores simbólicos raramente usados

O F# 6 adiciona orientação suave que desnormaliza o uso de :=, !, incr, e decr no F# 6 e além. O uso desses operadores e funções produz mensagens informativas que solicitam que você substitua seu código pelo uso explícito da Value propriedade.

Na programação em F#, as células de referência podem ser usadas para registros mutáveis alocados em heap. Embora sejam ocasionalmente úteis, raramente são necessários na codificação F# moderna, porque let mutable podem ser usados em vez disso. A biblioteca principal do F# inclui dois operadores := e ! duas funções incr e decr especificamente relacionadas a chamadas de referência. A presença desses operadores torna as células de referência mais centrais para a programação de F# do que precisam ser, exigindo que todos os programadores de F# conheçam esses operadores. Além disso, o ! operador pode ser facilmente confundido com a not operação em C# e outras linguagens, uma fonte potencialmente sutil de bugs ao traduzir código.

A lógica para essa mudança é reduzir o número de operadores que o programador de F# precisa saber e, assim, simplificar o F# para iniciantes.

Por exemplo, considere o seguinte código F# 5:

let r = ref 0

let doSomething() =
    printfn "doing something"
    r := !r + 1

Primeiro, as células de referência raramente são necessárias na codificação F# moderna, como let mutable normalmente pode ser usado em vez disso:

let mutable r = 0

let doSomething() =
    printfn "doing something"
    r <- r + 1

Se você usar células de referência, o F# 6 emitirá um aviso informativo solicitando que você altere a última linha para r.Value <- r.Value + 1, e vinculando-o a orientações adicionais sobre o uso apropriado de células de referência.

let r = ref 0

let doSomething() =
    printfn "doing something"
    r.Value <- r.Value + 1

Estas mensagens não são avisos; são "mensagens informativas" mostradas no IDE e na saída do compilador. F# permanece compatível com versões anteriores.

Este recurso implementa F # RFC FS-1111.

Ferramentas F#: .NET 6 o padrão para scripts no Visual Studio

Se você abrir ou executar um script F# (.fsx) no Visual Studio, por padrão, o script será analisado e executado usando o .NET 6 com execução de 64 bits. Essa funcionalidade estava em visualização nas versões posteriores do Visual Studio 2019 e agora está habilitada por padrão.

Para habilitar o script do .NET Framework, selecione Opções de>Ferramentas>F# Ferramentas F# Interativo.> Defina Usar scripts do .NET Core como false e reinicie a janela F# interativo. Essa configuração afeta a edição e a execução de scripts. Para habilitar a execução de 32 bits para scripts do .NET Framework, defina também F # Interactive de 64 bits como false. Não há nenhuma opção de 32 bits para scripts do .NET Core.

Ferramentas F#: fixe a versão SDK dos scripts F#

Se você executar um script usando dotnet fsi em um diretório que contém um arquivo global.json com uma configuração do SDK do .NET, a versão listada do SDK do .NET será usada para executar e editar o script. Este recurso está disponível nas versões posteriores do F# 5.

Por exemplo, suponha que haja um script em um diretório com o seguinte arquivo global.json especificando uma política de versão do SDK do .NET:

{
  "sdk": {
    "version": "5.0.200",
    "rollForward": "minor"
  }
}

Se você agora executar o script usando dotnet fsi, a partir deste diretório, a versão do SDK será respeitada. Este é um recurso poderoso que permite "bloquear" o SDK usado para compilar, analisar e executar seus scripts.

Se você abrir e editar seu script no Visual Studio e em outros IDEs, as ferramentas respeitarão essa configuração ao analisar e verificar seu script. Se o SDK não for encontrado, você precisará instalá-lo em sua máquina de desenvolvimento.

Em Linux e outros sistemas Unix, você pode combinar isso com um shebang para também especificar uma versão de linguagem para execução direta do script. Um shebang simples para script.fsx é:

#!/usr/bin/env -S dotnet fsi

printfn "Hello, world"

Agora o script pode ser executado diretamente com script.fsxo . Você pode combinar isso com uma versão de idioma específica e não padrão, como esta:

#!/usr/bin/env -S dotnet fsi --langversion:5.0

Nota

Essa configuração é ignorada pelas ferramentas de edição, que analisarão o script assumindo a versão mais recente do idioma.

Removendo recursos herdados

Desde o F# 2.0, alguns recursos herdados preteridos há muito tempo recebem avisos. O uso desses recursos no F# 6 gera erros, a menos que você use /langversion:5.0explicitamente o . Os recursos que dão erros são:

  • Vários parâmetros genéricos usando um nome de tipo postfix, por exemplo (int, int) Dictionary. Isso se torna um erro no F# 6. Em vez disso, deve ser utilizada a sintaxe Dictionary<int,int> padrão.
  • #indent "off". Isso se torna um erro.
  • x.(expr). Isso se torna um erro.
  • module M = struct … end . Isso se torna um erro.
  • Utilização de inputs *.ml e *.mli. Isso se torna um erro.
  • Utilização de (*IF-CAML*) ou (*IF-OCAML*). Isso se torna um erro.
  • Uso de , lor, lxor, lsl, lsr, ou asr como operadores infixland. Estas são palavras-chave infix em F# porque eram palavras-chave infix no OCaml e não estão definidas no FSharp.Core. O uso dessas palavras-chave emitirá um aviso.

Isso implementa o F# RFC FS-1114.