Novidades do F# 9
(learn.microsoft.com)-.NET 9 inclui o F# 9, que reduz problemas de segurança na interoperabilidade com C#/.NET por meio de tipos de referência anuláveis e melhorias nos diagnósticos do compilador
- Com propriedades
.Is*para uniões discriminadas, padrões ativos parciais com retornoboole computation expressions vazias, a sintaxe cotidiana do F# fica mais concisa - O FSharp.Core adiciona funções aleatórias para coleções e suporte a expressões de coleção do C#, facilitando o uso de coleções imutáveis do F# também em outros códigos .NET
- O compilador passa a revelar mais cedo problemas como uso incorreto de atributos, métodos IL acima de 65.520 e visibilidade de membros private
- Com otimizações em verificações de equality, intervalos inteiros e comprehensions de lista e array, alguns loops ficam entre 1,25× e 8× mais rápidos, e algumas comprehensions de array chegam a 10×
F# 9 no .NET 9
- O F# 9 inclui mudanças para tornar os programas mais seguros, resilientes e com melhor desempenho
- Está disponível no .NET 9, e o SDK mais recente do .NET pode ser baixado na página de downloads do .NET
- As principais mudanças foram desenvolvidas no repositório de código open source do F#
Mudanças nos recursos da linguagem
-
Tipos de referência anuláveis
- O F# foi projetado para evitar
null, mas ao ser usado com bibliotecas .NET escritas em C#,nullpode aparecer - O F# 9 permite expressar com segurança de tipos tipos de referência em que
nullé válido, comostring | null - Ao colocar
nullem umstringou acessar diretamente.Lengthem um valorstring | null, é emitido um aviso de possível nulidade - Se o caso
nullfor tratado primeiro em um pattern matching, os bindings seguintes passam a ser tratados como valores não nulos - Para retornar
nullem código genérico, é necessária uma restrição de tipo de referência, como'T : not struct - Mais detalhes em Nullable Reference Types in F# 9
- O F# foi projetado para evitar
-
Propriedades
.Is*em uniões discriminadas- Uniões discriminadas (discriminated unions) passam a ter propriedades
.Is*geradas automaticamente para cada caso - Por exemplo, se o tipo
Contacttiver os casosEmailePhone, será possível verificar se é um caso específico comperson.contact.IsEmail - Antes, a mesma verificação exigia escrever um
matchcomoEmail _ -> true | _ -> false
- Uniões discriminadas (discriminated unions) passam a ter propriedades
-
Retorno
boolem padrões ativos parciais- Padrões ativos parciais (partial active patterns) antes precisavam retornar
Some ()quando a correspondência tinha sucesso eNonequando falhava - No F# 9, o retorno
booltambém é aceito - Em um exemplo de comparação de string sem diferenciar maiúsculas e minúsculas, o resultado de
String.Equals(..., StringComparison.OrdinalIgnoreCase)pode ser retornado diretamente
- Padrões ativos parciais (partial active patterns) antes precisavam retornar
-
Prioridade para métodos de extensão quando há argumentos
- Algumas bibliotecas .NET definem métodos de extensão com o mesmo nome de propriedades próprias de um tipo
- No F# 9, para acompanhar esse padrão, quando há argumentos fornecidos o compilador resolve para o método de extensão em vez de falhar na checagem de tipos
- No exemplo, o método de extensão
X(f: Foo, i: int)com o mesmo nome da propriedadeXdeFoopode ser chamado comof.X(1), permitindo configurar a propriedade e encadear a chamada
-
Computation expressions vazias
- O F# 9 passa a oferecer suporte a computation expressions vazias
seq { }cria uma sequência vazia, e em códigos como DSLs de HTML é possível representar blocos vazios comop { }- Uma computation expression vazia resulta em uma chamada ao método
Zerodo builder - A sintaxe é mais natural que o antigo
builder { () }
Melhorias em diretivas hash e no F# Interactive
-
Argumentos não string em diretivas hash
- As diretivas hash do compilador antes aceitavam apenas argumentos de string entre aspas
- No F# 9, elas podem receber argumentos de tipos arbitrários
- Agora é possível usar
#nowarn 0070em vez de#nowarn "0070", e#time onem vez de#time "on"
-
#helpexpandido no F# Interactive- A diretiva
#helpdo F# Interactive mostra no REPL a documentação de objetos e funções - Os argumentos podem ser passados sem aspas
- Por exemplo,
#help List.map;;mostra descrição, parâmetros, valor de retorno, exemplos, nome completo e informações do assembly - Mais detalhes em Enhancing #help in F# Interactive blog post
- A diretiva
-
Suporte ao prefixo
FSem#nowarn- Antes, ao usar algo como
#nowarn "FS0057", ocorria o erroInvalid warning number 'FS0057'mesmo quando o número do aviso estava correto - No F# 9, números de aviso com o prefixo
FStambém são aceitos #nowarn 57,#nowarn 0057,#nowarn FS0057e as formas em string"57","0057","FS0057"passam a funcionar- Dentro de um projeto, vale a pena manter um mesmo estilo
- Antes, ao usar algo como
Melhorias em segurança e diagnósticos do compilador
-
Aviso para posição incorreta de
[<TailCall>]- O F# 9 emite aviso quando o atributo
[<TailCall>]é aplicado em um local inadequado - Os exemplos incluem aplicação em função não recursiva, valor de binding
lete valor de bindingletrecursivo - Esses atributos não afetam o comportamento do código, mas podem confundir quem o lê
- O F# 9 emite aviso quando o atributo
-
Aplicação mais rígida de
AttributeTargets- O compilador passa a aplicar corretamente
AttributeTargetsem valoreslet, funções, declarações de union case, construtores implícitos,structeclass - Isso ajuda a evitar bugs pouco óbvios, como esquecer o argumento
unitem um teste do Xunit - Antes,
[<Fact>] let ``this test always fails`` = Assert.True(false)não era uma função de verdade, então o test runner a ignorava e odotnet testpassava - Agora, ocorre o erro
error FS0842: This attribute is not valid for use on this language element
- O compilador passa a aplicar corretamente
-
Recuperação do parser
- Melhorias na recuperação do parser mantêm funcionando recursos de ferramenta, como syntax highlighting, mesmo quando o código está sintaticamente incompleto durante a edição
- Os casos tratados incluem padrão
asincompleto, object expression, declaração de enum case, declaração de record, padrões complexos de primary constructor, unresolved long identifier, cláusulamatchvazia e ausência de campos e tipos de campo em union cases
-
Precisão de mensagens e posições de diagnóstico
- O F# 9 adiciona novas mensagens de diagnóstico e posições mais precisas para esses diagnósticos
- Entre os casos estão métodos
overrideambíguos em object expressions,abstract memberem classes não abstratas, propriedades com o mesmo nome de um caso de união discriminada, divergência no número de argumentos de active patterns, unions com campos duplicados e uso conjunto deuse!eand!em computation expressions - Classes com mais de 65.520 métodos no IL gerado agora produzem um novo erro em tempo de compilação
- Essas classes não podem ser carregadas pelo CLR, o que levaria a erro em tempo de execução
-
Opção de visibilidade real
- O F# grava membros private como internal no IL, o que permitia acesso indevido a membros private a partir de projetos não F# que pudessem acessar um projeto F# via
InternalsVisibleTo - O F# 9 oferece o sinalizador opcional do compilador
--realsig+para corrigir esse comportamento - No
.fsproj, ele pode ser ativado com<RealSig>true</RealSig> - Isso permite verificar se a solução depende do comportamento antigo
- O F# grava membros private como internal no IL, o que permitia acesso indevido a membros private a partir de projetos não F# que pudessem acessar um projeto F# via
Mudanças na biblioteca padrão FSharp.Core
-
Funções aleatórias para coleções
- Os módulos
List,ArrayeSeqpassam a incluir funções de amostragem aleatória e embaralhamento - Isso facilita o uso de F# em cenários comuns que exigem aleatoriedade, como ciência de dados, machine learning e desenvolvimento de jogos
- Todas as funções têm três variações
- uma variação que usa uma instância compartilhada de
Random, implícita e thread-safe - uma variação que recebe uma instância de
Randomcomo argumento - uma variação que recebe uma função
randomizerpersonalizada, que deve retornar valoresfloatmaiores ou iguais a 0.0 e menores que 1.0
- uma variação que usa uma instância compartilhada de
- As funções fornecidas são
Shuffle,Choice,ChoiceseSample, cada uma com três variações - A lista completa de funções e variações está em RFC #1135
- Os módulos
-
Comportamento de cada função aleatória
Shuffleretorna uma nova coleção do mesmo tipo e tamanho, com os itens embaralhados com peso uniforme em relação ao tamanho da coleção- Para arrays, também existe uma variação
InPlaceque embaralha os itens no próprio array Choiceretorna um único elemento aleatório com peso uniforme em relação ao tamanho da coleçãoChoicesseleciona N elementos da coleção de entrada em ordem aleatória, e o mesmo elemento pode ser escolhido várias vezesSampleseleciona N elementos da coleção de entrada em ordem aleatória, sem escolher o mesmo elemento duas vezes- Em
Sample, N não pode ser maior que o tamanho da coleção
-
Construtor sem argumentos em
CustomOperationAttribute- Foi adicionado um construtor sem argumentos a
CustomOperationAttribute, facilitando a criação de operações personalizadas em builders de computation expression - Na maioria dos casos, o nome explícito é igual ao nome do método, então agora é possível usar
[<CustomOperation>]em vez de[<CustomOperation("bar")>]
- Foi adicionado um construtor sem argumentos a
-
Suporte a expressões de coleção do C#
- No C#, listas e sets de F# podem ser inicializados com expressões de coleção (collection expressions)
- Por exemplo, em vez de
SetModule.FromArray([1, 2, 3]), é possível escreverFSharpSet<int> mySet = [ 1, 2, 3 ]; - Coleções imutáveis do F# podem ser úteis quando se precisa de structural equality, ausente nas coleções
System.Collections.Immutable
Melhorias de desempenho
-
Otimização em verificações de equality
- As verificações de equality ficam mais rápidas e com menos alocações de memória
- Em um exemplo que usa
Array.containsem um array de tiposstructpara procurar um valor inexistente, antes havia boxing 1.000 vezes; agora isso não acontece - Em benchmarks de funções de array com
structde 2 membros, o tempo médio deArrayContainsNonexistingcaiu de 5.190,95ns para 766,005ns, e as alocações foram de 24.000B para 0 ArrayTryFindNonexistingcaiu de 5.139,58ns para 1.140,515ns, e as alocações passaram de 24.024B para 24B- Mais detalhes em F# Developer Stories: How we’ve finally fixed a 9-year-old performance issue
-
Compartilhamento de campos em uniões discriminadas
struct- Quando vários casos de uma união discriminada
structtêm nomes e tipos de campo iguais, eles podem compartilhar a mesma posição de memória - Isso reduz o uso de memória da
struct - No exemplo, o tamanho da união discriminada
structque compartilha campos baseados emint64é de 16 bytes - Na versão antiga, em que cada caso precisava usar nomes de campo exclusivos, o tamanho era de 60 bytes
- Como antes nomes de campo iguais não eram permitidos, não há problema de compatibilidade binária
- Quando vários casos de uma união discriminada
-
Otimização de intervalos inteiros
- O compilador passa a gerar código otimizado em mais casos para expressões
start..finishestart..step..finish - Antes, só havia otimização quando o tipo era
int/int32e o passo era a constante1ou-1 - Outros tipos inteiros e outros valores de passo usavam uma implementação ineficiente baseada em
IEnumerable - Agora todos esses casos são otimizados
- Em
for … in start..finish do …,[start..step..finish]e[for n in start..finish -> f n], houve ganhos de 1,25× até 8×
- O compilador passa a gerar código otimizado em mais casos para expressões
-
Otimização de comprehensions de lista e array
- Em comprehensions de lista e array, a forma
for x in xs -> …foi otimizada - A melhoria é especialmente visível em arrays
- A velocidade pode aumentar em até 10×, enquanto o volume de alocações cai para metade ou até um quarto
- Em comprehensions de lista e array, a forma
Melhorias nas ferramentas do Visual Studio
-
live buffers ativado por padrão
- O recurso live buffers do Visual Studio antes era opcional, mas após testes suficientes passou a vir ativado por padrão
- O compilador em segundo plano que alimenta a IDE usa os buffers de arquivos ainda não salvos
- Assim, as mudanças são aplicadas mesmo sem salvar o arquivo no disco
- Antes, podia haver comportamento inesperado ao renomear símbolos que estavam em arquivos editados mas ainda não salvos
-
Code fix para remover parênteses desnecessários
- O Visual Studio agora oferece um code fix para remover parênteses desnecessários
- Por exemplo,
let f (x) = xpode virarlet f x = x, elet _ = (2 * 2) + 3pode virarlet _ = 2 * 2 + 3 - O objetivo é reduzir parênteses que mais atrapalham do que ajudam, sem mexer nos que melhoram a clareza
-
Suporte a visualizações personalizadas em projetos F#
- Visualizadores do depurador do Visual Studio (debugger visualizer) agora funcionam também em projetos F#
-
Tooltip de assinatura no meio do pipeline
- Antes, quando parâmetros curried complexos já tinham sido aplicados a uma função no meio do pipeline, o signature help não era exibido
- Agora, é mostrado um tooltip de assinatura para o próximo parâmetro
1 comentários
Opiniões do Hacker News
F# continuou sendo minha linguagem favorita desde que tive contato com ela pela primeira vez na universidade.
Ela estava muito à frente do C# em recursos como unions, segurança contra nulos, pattern matching, records, inferência de tipos mais forte e restrições genéricas.
É bom que o C# tenha adotado esses recursos com o tempo, mas é uma pena que eles tenham entrado de maneiras incompatíveis entre si.
Como o investimento em F# é muito menor que em C#, ela também ficou para trás no ritmo de inovação, mas ainda é uma excelente linguagem, em geral compatível com o ecossistema .NET, e consegue entregar o mesmo desempenho do C# com muito menos boilerplate.
Dá para lidar com isso com bastante facilidade escrevendo um projeto auxiliar em C# que contenha o “código de cola” necessário.
Fico curioso se há algum outro problema específico em mente.
O F# 9 também oferece suporte ao uso de argumentos genéricos
ref struct, adicionado recentemente ao C#, e, pelo que sei, há planos para introduzir recursos definidos no próprio F#.Até agora, ele tem feito um trabalho impressionante para acompanhar, e merece muito mais reconhecimento.
O trecho que diz que “classes com mais de 65.520 métodos no IL gerado agora geram um novo erro em tempo de compilação. Essas classes não podem ser carregadas pelo CLR, causando erro em tempo de execução” é difícil até de imaginar.
De qualquer forma, F# é uma linguagem excelente.
Acho que é a segunda melhor coisa que a Microsoft já lançou, depois do Excel, e torna o .NET uma plataforma razoável.
É uma linguagem relativamente fácil de ensinar para quem já está familiarizado com JS ou TS, e também é bastante produtiva.
É usada em vários contextos, de motores de jogos e backends corporativos a apps desktop.
Acho que a Microsoft cometeu alguns erros no início que desaceleraram seu crescimento, mas, como linguagem de uso geral, ela é realmente boa e relativamente fácil de aprender.
Alguns anos atrás, mexi um pouco nele com uma IDE leve chamada LINQPad, mas depois não continuei acompanhando os prós e contras nem a evolução.
https://www.linqpad.net/
Na Phosphor, tomamos uma grande decisão de apostar a empresa e a direção técnica em F# por alguns anos.
Depois de mais de um ano tentando, reescrevemos completamente a aplicação em TypeScript e Rust.
O produto que estamos criando é uma ferramenta de programação para usuários finais, então a fronteira tradicional entre frontend e backend fica difusa, e o ecossistema .NET não se encaixou bem nisso.
A ideia original era compilar código F# com Fable para JS, Rust, .NET etc., mantendo a segurança de tipos entre várias tecnologias e fazendo a interoperabilidade necessária.
Na prática, a interoperabilidade entre várias bibliotecas foi muito mais difícil do que esperávamos, e gerenciar e atualizar diversas dependências e bindings foi realmente doloroso.
Ainda acho válida a premissa de que F# produz código bonito e eficiente, mas, pelo ecossistema e pelo modelo de design, vejo que ele se encaixa bem apenas em aplicações com uma fronteira tradicional clara entre frontend e backend.
Nesse caso, F# acabaria sendo usado apenas no backend.
As tecnologias que mais me empolgam agora são Effect e Moonbit, que usamos internamente.
A biblioteca Schema do Effect preenche muitas lacunas do sistema de tipos do TS, e Moonbit parece um F# moderno sem a dependência de MS/.NET.
Moonbit foi projetado pelo criador do ReScript, que pode ser visto como um Fable para OCaml, é muito bem desenhado e compila diretamente para JS, WASM e saída nativa otimizados.
Estamos usando Effect em produção, e Moonbit ainda não, mas o potencial dele como linguagem criada para um mundo AI-first é bastante impressionante.
Escrevemos a lógica de negócio central e os validadores em F#, enquanto o restante do app frontend era em TypeScript e o backend em C#.
Ou seja, a estrutura deixava apenas a lógica central e as validações em F#, e todo o input/output ficava a cargo de TS e C#.
Lembro que era um produto parecido e escrito em F#, mas não acompanhei a situação recente.
Effect é muito bom, e eu gostaria que existisse também em outras linguagens além de TypeScript.
MoonBit parece uma linguagem de programação proprietária própria, então fico hesitante em migrar para ela em vez de uma linguagem bem conhecida; tenho curiosidade sobre como você enxerga essa questão.
Tive uma disciplina de criptografia em que podíamos escolher qualquer linguagem que rodasse em .NET, e o trabalho que fiz em F# ficou muito mais legível que os dos outros.
Eu gostaria de usá-lo com mais frequência, mas o trabalho de ciência de dados é quase 100% em Python.
F# 9 também se beneficia de quase todas as melhorias de desempenho do próprio .NET 9.
Em especial, as melhorias em object escape analysis são grandes.
https://devblogs.microsoft.com/dotnet/performance-improvemen...
Sinto muita falta da época em que trabalhava com F#.
É uma linguagem muito produtiva, então é prazeroso até só acompanhar as atualizações.
Considerando o tamanho da comunidade e a indiferença que a Microsoft às vezes demonstra, acho que o suporte de ferramentas era bastante bom.
O maior incômodo era a precisão da cobertura de testes de código.
Recentemente mexi um pouco com F# e, vindo de Python, gostei muito do fato de poder experimentar várias coisas no REPL
Fico curioso se desenvolvedores experientes de F# também o usam assim
Neste inverno, quero criar um pequeno projeto de backend web para conhecer melhor a linguagem e o ecossistema
Para HTTP, ouvi dizer que o Oxpecker é bom, mas queria saber se há alguma recomendação de cliente ou driver para PostgreSQL
Não gosto de ORM
https://lanayx.github.io/Oxpecker/
https://monazita.gitlab.io/monazita/ foi feito para projetos pessoais enquanto se aprendia F#; basicamente funciona, mas ainda há espaço para refinamento e é específico para PostgreSQL
https://github.com/jacentino/DbFun é mais refinado que o projeto anterior e oferece suporte a vários bancos de dados
Eu começaria por aí
Quando a API não está clara, mesmo dentro da base de código real eu clico em “Send to F# interactive” para executar e experimentar dentro do módulo
Também uso para testar bibliotecas novas, fazer benchmarks rápidos ou como substituto de scripts em PowerShell
Com o shebang
#!/usr/bin/env -S dotnet fsi, é possível tornar scripts F# executáveis, então costumo usá-los como alternativa a bash/Python em scripts ao redor de projetos .NET que já têm o dotnet-sdk instaladoEm geral, executam mais rápido e costumam ser igualmente concisos
Pessoalmente, sinto que a sintaxe e os idioms do F# se encaixam melhor do que C# na programação via REPL, juntando pequenos trechos de código
C# normalmente exige mais estrutura orientada a objetos
Fico curioso sobre como funciona o versionamento do F#
Há muitas melhorias de qualidade de vida que parecem boas, mas, do ponto de vista de versionamento semântico, não há quebra de compatibilidade que pareça justificar uma mudança de versão principal, e também não parece ser um salto de recursos de linguagem grande o suficiente para um projeto que não usa versionamento semântico ir de 8 para 9
Outro comentário mencionou o .NET 9 lançado recentemente; queria saber se é para acompanhar a numeração de versões do .NET
Não sei se a mudança foi intencionalmente feita para coincidir com as versões do .NET ou se foi coincidência
Por exemplo, C# lançou o C# 13 junto com o .NET 9
dotnet buildIsso inclui msbuild, compiladores, pacotes NuGet etc.
Por exemplo, a equipe do F# poderia ter lançado mudanças na linguagem entre as versões 8 e 9, mas, mesmo se não tivesse feito isso, poderia ter lançado por algum motivo mudanças no compilador ou no msbuild que exigissem o .NET 9
Normalmente, desenvolvedores podem simplesmente atualizar o código para a versão mais recente do runtime, a menos que precisem esperar o .NET mais novo estar instalado no ambiente de implantação
Hoje em dia, mudanças no MSBuild que criam deployments self-contained reduziram essa necessidade, mas é algo bom de saber
Só que C# está 4 números à frente
De qualquer forma, acho que versionamento semântico é superestimado
Fico curioso sobre o estado do F# como alternativa ao C# para criar apps GUI no Windows
Também queria saber se há empresas usando F# para esse tipo de finalidade
https://github.com/fsprojects/Avalonia.FuncUI
https://fabulous.dev/ tem como alvo Avalonia/MAUI/Xamarin
https://github.com/kekyo/epoxy oferece suporte a Avalonia e WPF
Se você tiver interesse em saber se empresas usam isso para esse fim, posso perguntar por aí
Nunca usei F# diretamente, mas, quando dei uma olhada, este material pareceu excelente: https://fsharpforfunandprofit.com/
É bom tanto para desenvolvedores C# experientes quanto para pessoas relativamente novas em programação
Hoje em dia quase não é atualizado, então é difícil encontrar discussões sobre os novos recursos do F# 9, mas os artigos existentes são excelentes para entender conceitos como
applyebind