Compartilhar via


Novidades no F# 10

O F# 10 oferece várias melhorias na linguagem F#, biblioteca FSharp.Core e ferramentas. Esta versão é uma versão de refinamento focada em clareza, consistência e desempenho, com melhorias pequenas, mas significativas, que tornam seu código cotidiano mais legível e robusto. O F# 10 é fornecido com o .NET 10 e o Visual Studio 2026.

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

Introdução

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

Supressão de avisos com escopo

Agora você pode suprimir avisos em seções específicas do código usando a nova #warnon diretiva. Isso combina com a diretiva existente #nowarn para fornecer controle preciso sobre onde cada aviso se aplica.

Anteriormente, quando você usava #nowarn, desabilitava um aviso para o restante do arquivo, o que poderia suprimir problemas legítimos em outras partes do arquivo. Vamos examinar um exemplo motivador:

// We know f is never called with None.
let f (Some a) =    // creates warning 25, which we want to suppress
    // 2000 loc, where the incomplete match warning is beneficial

Se você adicionar #nowarn 25 acima da definição de função, ela desabilita FS0025 para todo o restante do arquivo.

Com f# 10, agora você pode marcar a seção exata em que deseja suprimir o aviso:

#nowarn 25
let f (Some x) =    // FS0025 suppressed
#warnon 25
    // FS0025 enabled again

Por outro lado, se um aviso estiver desabilitado globalmente (por exemplo, por meio de um sinalizador do compilador), você poderá habilitá-lo localmente com #warnon. Essa diretiva será aplicada até que uma correspondência #nowarn ou o final do arquivo.

Notas de compatibilidade importantes:

Esse recurso inclui várias alterações que melhoram a consistência das #nowarn/#warnon diretivas. Estas são alterações disruptivas:

  • O compilador não permite mais diretivas de aviso de várias linhas e vazias.
  • O compilador não permite mais espaço em branco entre # e nowarn.
  • Você não pode usar cadeias de caracteres tríplices, interpoladas ou verbatim para números de aviso.

O comportamento do script também foi alterado. Anteriormente, quando você adicionava uma #nowarn diretiva em qualquer lugar em um script, ela se aplicava a toda a compilação. Agora, seu comportamento em scripts corresponde ao nos arquivos .fs, aplicando-se somente até o final do arquivo ou um correspondente #warnon.

Esse recurso implementa o RFC FS-1146.

Modificadores de acesso em acessadores de propriedade automática

Um padrão comum na programação orientada a objetos é criar um estado publicamente legível, mas de forma privada mutável. Antes do F# 10, você precisava de sintaxe de propriedade explícita com campos de backup (variáveis ocultas que armazenam os valores reais da propriedade) para conseguir isso, o que adicionou código repetitivo:

type Ledger() =
    [<DefaultValue>] val mutable private _Balance: decimal
    member this.Balance with public get() = this._Balance and private set v = this._Balance <- v

Com o F# 10, agora você pode aplicar modificadores de acesso diferentes a acessadores de propriedade individuais. Isso permite que você especifique diferentes níveis de acesso para o getter e o setter de uma propriedade, tornando o padrão muito mais simples:

type Ledger() =
    member val Balance = 0m with public get, private set

Você pode colocar um modificador de acesso antes do nome da propriedade (aplicando-se a ambos os acessadores) ou antes de acessadores individuais, mas não ambos simultaneamente.

Observe que esse recurso não se estende a arquivos de assinatura (.fsi). A assinatura correta para o Ledger exemplo acima é:

type Ledger() =
    member Balance : decimal
    member private Balance : decimal with set

Esse recurso implementa o RFC FS-1141.

Parâmetros opcionais do ValueOption

Agora você pode usar uma representação baseada em ValueOption<'T> struct para parâmetros opcionais. Quando você aplica o [<Struct>] atributo a um parâmetro opcional, o compilador usa ValueOption<'T> em vez do tipo baseado em option referência. Isso evita alocações de heap (memória alocada no heap gerenciado que requer coleta de lixo) para o wrapper de opção, o que é benéfico no código crítico ao desempenho.

Anteriormente, F# sempre usava o tipo alocado no heap option para parâmetros opcionais, mesmo quando o parâmetro estava ausente.

// Prior to F# 10: always uses reference option
type X() =
    static member M(?x : string) =
        match x with
        | Some v -> printfn "Some %s" v
        | None -> printfn "None"

No F# 10, você pode usar o atributo [<Struct>] para aproveitar o ValueOption baseado em struct:

type X() =
    static member M([<Struct>] ?x : string) =
        match x with
        | ValueSome v -> printfn "ValueSome %s" v
        | ValueNone -> printfn "ValueNone"

Isso elimina as alocações de heap quando o argumento está ausente, algo que beneficia o código em situações críticas de desempenho.

Escolha essa opção baseada em struct para valores pequenos ou tipos construídos com frequência em que a pressão de alocação é importante. Use o padrão de referência baseado em option ao depender de auxiliares para correspondência de padrões existentes, precisar de semântica de referência ou se a diferença de desempenho for insignificante. Esse recurso fortalece a paridade com outros constructos de linguagem F# que já dão suporte ValueOption.

Suporte à chamada final em expressões de computação

F# 10 adiciona otimizações de recursão final para expressões de computação. Os construtores de expressão de computação agora podem optar por essas otimizações implementando métodos especiais.

Quando o compilador traduz expressões de computação em código F# regular (um processo chamado de desugarização), ele reconhece quando uma expressão como return!, yield! ou do! aparece em posição final. Se o construtor fornecer os seguintes métodos, o compilador roteia essas chamadas para pontos de entrada otimizados:

  • ReturnFromFinal - chamado para uma cauda return! (cai de volta para ReturnFrom se ausente)
  • YieldFromFinal - chamado para um tail yield! (retorna para YieldFrom se ausente)
  • Para um terminaldo!, o compilador prefereReturnFromFinal, entãoYieldFromFinal, antes de voltar ao caminho normal Bind

Esses *Final membros são opcionais e existem puramente para habilitar a otimização. Os construtores que não fornecem esses membros mantêm a semântica existente inalterada.

Por exemplo:

coroutine {
    yield! subRoutine() // tail position -> YieldFromFinal if available
}

No entanto, em uma posição não terminal.

coroutine {
    try
        yield! subRoutine() // not tail -> normal YieldFrom
    finally ()
}

Observação de compatibilidade importante:

Essa alteração poderá ser disruptiva se um construtor de expressões de computação já define membros com esses nomes. Na maioria dos casos, os construtores existentes continuam funcionando sem modificação quando compilados com F# 10. Compiladores mais antigos ignorarão os novos *Final métodos, portanto, os construtores que devem permanecer compatíveis com versões anteriores do compilador não devem assumir que o compilador invocará esses métodos.

Esse recurso implementa o RFC FS-1330.

Vinculações tipadas em expressões de computação sem parênteses

F# 10 remove o requisito de parênteses ao adicionar anotações de tipo a associações de expressão de computação. Agora você pode adicionar anotações de tipo nas let!, use! e and! associações usando a mesma sintaxe que nas associações comuns let.

Anteriormente, você precisava usar parênteses para anotações de tipo:

async {
    let! (a: int) = fetchA()
    and! (b: int) = fetchB()
    use! (d: MyDisposable) = acquireAsync()
    return a + b
}

No F# 10, você pode escrever anotações de tipo sem parênteses:

async {
    let! a: int = fetchA()
    and! b: int = fetchB()
    use! d: MyDisposable = acquireAsync()
    return a + b
}

Permitir _ em associações use!

Agora você pode usar o padrão de descarte (_) em use! vinculações dentro de expressões computacionais. Isso alinha o comportamento de use! às associações regulares use.

Anteriormente, o compilador rejeitava o padrão de descarte em use! vinculações, forçando você a criar identificadores descartáveis.

counterDisposable {
    use! _ignored = new Disposable()
    // logic
}

No F# 10, você pode usar o padrão de descarte diretamente:

counterDisposable {
    use! _ = new Disposable()
    // logic
}

Isso esclarece a intenção ao associar recursos assíncronos cujos valores são necessários apenas para o gerenciamento de ciclo de vida.

Rejeição de módulos pseudo-aninhados em tipos

O compilador agora gera um erro quando você coloca uma declaração module com recuo no mesmo nível estrutural em uma definição de tipo. Isso reforça a validação estrutural para rejeitar o posicionamento enganoso do módulo dentro dos tipos.

Anteriormente, o compilador aceitava module declarações recuadas dentro de definições de tipo, mas, na verdade, criava esses módulos como irmãos do tipo em vez de aninhá-los dentro dele.

type U =
    | A
    | B
    module M = // Silently created a sibling module, not nested
        let f () = ()

Com f# 10, esse padrão gera o erro FS0058, forçando você a esclarecer sua intenção com o posicionamento adequado do módulo:

type U =
    | A
    | B

module M =
    let f () = ()

Aviso de descontinuação para item omitido seq

O compilador agora avisa sobre expressões de sequência nua que omitem o seq construtor. Quando você usa chaves de intervalo nuas como { 1..10 }, você verá um aviso de descontinuação incentivando o uso do formulário explícito seq { ... }.

Historicamente, F# permitiu uma sintaxe "sequence comprehension lite" de caso especial em que você poderia omitir a palavra-chave seq.

{ 1..10 } |> List.ofSeq  // implicit sequence, warning FS3873 in F# 10

No F# 10, o compilador alerta sobre esse padrão e incentiva a forma explícita:

seq { 1..10 } |> List.ofSeq

No momento, isso é um aviso, não um erro, dando-lhe tempo para atualizar sua base de código. Se você quiser suprimir esse aviso, use a propriedade NoWarn no arquivo do projeto ou a diretiva #nowarn localmente e insira o número do aviso: 3873.

O formulário explícito seq melhora a clareza e a consistência do código com outras expressões de computação. Versões futuras do F# podem fazer disso um erro, portanto, recomendamos adotar a sintaxe explícita ao atualizar seu código.

Esse recurso implementa o RFC FS-1033.

Aplicação de alvo de atributo

O F# 10 impõe a validação de destino de atributo em todos os constructos de linguagem. O compilador agora valida se os atributos são aplicados apenas aos destinos pretendidos, verificando valores vinculados por 'let', funções, casos de união, construtores implícitos, structs e classes.

Anteriormente, o compilador permitia silenciosamente que você aplicasse incorretamente atributos a destinos incompatíveis. Isso causou bugs sutis, como atributos de teste sendo ignorados quando você esqueceu () de fazer uma função:

[<Fact>]
let ``this is not a function`` = // Silently ignored in F# 9, not a test!
    Assert.True(false)

No F# 10, o compilador impõe destinos de atributo e gera um aviso quando os atributos são mal aplicados:

[<Fact>]
//^^^^ - warning FS0842: This attribute cannot be applied to property, field, return value. Valid targets are: method
let ``this is not a function`` =
    Assert.True(false)

Observação de compatibilidade importante:

Essa é uma alteração significativa que pode revelar problemas anteriormente silenciosos em sua base de código. Os primeiros erros impedem problemas na descoberta de testes e garantem que atributos como analisadores e decoradores entrem em vigor conforme o esperado.

Suporte para expressões de tarefa and!

Agora você pode aguardar várias tarefas simultaneamente usando and! em expressões de tarefa. Usar task é uma maneira popular de trabalhar com fluxos de trabalho assíncronos em F#, especialmente quando você precisa de interoperabilidade com C#. No entanto, até agora, não havia nenhuma maneira concisa de aguardar várias tarefas simultaneamente em uma expressão de computação.

Talvez você tenha começado com um código que aguardava a execução de cálculos em sequência:

// Awaiting sequentially
task {
    let! a = fetchA()
    let! b = fetchB()
    return combineAB a b
}

Se você quisesse alterá-lo para aguardá-los simultaneamente, normalmente usaria Task.WhenAll:

// Use explicit Task combinator to await concurrently
task {
    let ta = fetchA()
    let tb = fetchB()
    let! results = Task.WhenAll([| ta; tb |])
    return combineAB ta.Result tb.Result
}

No F# 10, você pode usar and! para uma abordagem mais idiomática:

task {
    let! a = fetchA()
    and! b = fetchB()
    return combineAB a b
}

Isso combina a semântica da versão simultânea com a simplicidade da versão sequencial.

Esse recurso implementa a sugestão de linguagem F# nº 1363 e é implementado como uma adição à FSharp.Core biblioteca. A maioria dos projetos obtém automaticamente a versão mais recente do compilador FSharp.Core, a menos que fixem explicitamente uma versão do compilador. Nesse caso, você precisará atualizá-lo para usar esse recurso.

Melhor corte por padrão

F# 10 elimina uma antiga pequena barreira com a otimização das assemblies F#. O corte é o processo de remoção de código não utilizado do aplicativo publicado para reduzir seu tamanho. Você não precisa mais manter manualmente um arquivo ILLink.Substitutions.xml apenas para remover grandes blobs de recursos de metadados F# (dados de assinatura e otimização que o compilador usa, mas que seu aplicativo não precisa durante a execução).

Quando você publica com corte habilitado (PublishTrimmed=true), o build do F# agora gera automaticamente um arquivo de substituições incorporado que visa recursos F# exclusivos para ferramentas.

Anteriormente, você precisava manter manualmente esse arquivo para remover os metadados. Isso aumentou a carga de manutenção e foi fácil de esquecer.

O resultado é uma saída menor por padrão, menos código repetitivo a ser mantido e um risco de manutenção menor. Se você precisar de controle manual completo, ainda poderá adicionar seu próprio arquivo de substituições. Você pode desativar a geração automática com a <DisableILLinkSubstitutions>false</DisableILLinkSubstitutions> propriedade.

Compilação paralela na versão prévia

Uma atualização empolgante para usuários do F# que buscam reduzir os tempos de compilação: os recursos de compilação paralela estão se estabilizando. A partir do .NET 10, três recursos: verificação de tipo baseada em grafo, geração de código IL paralela e otimização paralela são agrupados na propriedade do ParallelCompilation projeto.

O F# 10 habilita essa configuração por padrão para projetos que usam LangVersion=Preview. Planejamos habilitá-lo para todos os projetos no .NET 11.

Certifique-se de experimentar e verificar se isso acelera sua compilação. Para habilitar a compilação paralela no F# 10:

<PropertyGroup>
    <ParallelCompilation>true</ParallelCompilation>
    <Deterministic>false</Deterministic> <!-- Note: deterministic builds don't get the benefits of parallel compilation -->
</PropertyGroup>

Se você quiser recusar enquanto ainda estiver desfrutando de outros recursos de visualização, defina ParallelCompilation como false:

<PropertyGroup>
    <LangVersion>Preview</LangVersion>
    <ParallelCompilation>false</ParallelCompilation>
</PropertyGroup>

A compilação paralela pode reduzir significativamente os tempos de compilação para projetos com vários arquivos e dependências.

Cache de subsumpição de tipo

O compilador agora armazena em cache verificações de relação de tipo para acelerar a inferência de tipo e melhorar o desempenho do IDE, especialmente ao trabalhar com hierarquias de tipo complexas. Ao armazenar e reutilizar resultados de verificações de subsumpição anteriores, o compilador evita cálculos redundantes que anteriormente retardavam a compilação e o IntelliSense.

Gerenciando o cache:

Na maioria das vezes, o cache de subsumo de tipo melhora o desempenho sem necessidade de configuração. No entanto, se você tiver maior volume de memória ou aumento do uso da CPU (devido aos trabalhos de manutenção de cache), poderá ajustar o comportamento do cache:

  • Para desabilitar totalmente o cache, defina <LangVersion>9</LangVersion> no arquivo de projeto para voltar ao comportamento F# 9.
  • Para desativar a remoção assíncrona de cache, que aumenta a pressão sobre as threads, e usar a remoção síncrona, defina a variável de ambiente FSharp_CacheEvictionImmediate=1.