1 pontos por GN⁺ 2024-11-11 | 1 comentários | Compartilhar no WhatsApp

-.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 retorno bool e 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#, null pode aparecer
    • O F# 9 permite expressar com segurança de tipos tipos de referência em que null é válido, como string | null
    • Ao colocar null em um string ou acessar diretamente .Length em um valor string | null, é emitido um aviso de possível nulidade
    • Se o caso null for tratado primeiro em um pattern matching, os bindings seguintes passam a ser tratados como valores não nulos
    • Para retornar null em 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
  • 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 Contact tiver os casos Email e Phone, será possível verificar se é um caso específico com person.contact.IsEmail
    • Antes, a mesma verificação exigia escrever um match como Email _ -> true | _ -> false
  • Retorno bool em padrões ativos parciais

    • Padrões ativos parciais (partial active patterns) antes precisavam retornar Some () quando a correspondência tinha sucesso e None quando falhava
    • No F# 9, o retorno bool també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
  • 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 propriedade X de Foo pode ser chamado como f.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 como p { }
    • Uma computation expression vazia resulta em uma chamada ao método Zero do 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 0070 em vez de #nowarn "0070", e #time on em vez de #time "on"
  • #help expandido no F# Interactive

    • A diretiva #help do 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
  • Suporte ao prefixo FS em #nowarn

    • Antes, ao usar algo como #nowarn "FS0057", ocorria o erro Invalid warning number 'FS0057' mesmo quando o número do aviso estava correto
    • No F# 9, números de aviso com o prefixo FS também são aceitos
    • #nowarn 57, #nowarn 0057, #nowarn FS0057 e as formas em string "57", "0057", "FS0057" passam a funcionar
    • Dentro de um projeto, vale a pena manter um mesmo estilo

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 let e valor de binding let recursivo
    • Esses atributos não afetam o comportamento do código, mas podem confundir quem o lê
  • Aplicação mais rígida de AttributeTargets

    • O compilador passa a aplicar corretamente AttributeTargets em valores let, funções, declarações de union case, construtores implícitos, struct e class
    • Isso ajuda a evitar bugs pouco óbvios, como esquecer o argumento unit em 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 o dotnet test passava
    • Agora, ocorre o erro error FS0842: This attribute is not valid for use on this language element
  • 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 as incompleto, object expression, declaração de enum case, declaração de record, padrões complexos de primary constructor, unresolved long identifier, cláusula match vazia 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 override ambíguos em object expressions, abstract member em 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 de use! e and! 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

Mudanças na biblioteca padrão FSharp.Core

  • Funções aleatórias para coleções

    • Os módulos List, Array e Seq passam 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 Random como argumento
      • uma variação que recebe uma função randomizer personalizada, que deve retornar valores float maiores ou iguais a 0.0 e menores que 1.0
    • As funções fornecidas são Shuffle, Choice, Choices e Sample, cada uma com três variações
    • A lista completa de funções e variações está em RFC #1135
  • Comportamento de cada função aleatória

    • Shuffle retorna 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 InPlace que embaralha os itens no próprio array
    • Choice retorna um único elemento aleatório com peso uniforme em relação ao tamanho da coleção
    • Choices seleciona N elementos da coleção de entrada em ordem aleatória, e o mesmo elemento pode ser escolhido várias vezes
    • Sample seleciona 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")>]
  • 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 escrever FSharpSet<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.contains em um array de tipos struct para procurar um valor inexistente, antes havia boxing 1.000 vezes; agora isso não acontece
    • Em benchmarks de funções de array com struct de 2 membros, o tempo médio de ArrayContainsNonexisting caiu de 5.190,95ns para 766,005ns, e as alocações foram de 24.000B para 0
    • ArrayTryFindNonexisting caiu 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 struct tê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 struct que compartilha campos baseados em int64 é 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
  • Otimização de intervalos inteiros

    • O compilador passa a gerar código otimizado em mais casos para expressões start..finish e start..step..finish
    • Antes, só havia otimização quando o tipo era int/int32 e o passo era a constante 1 ou -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×
  • 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

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) = x pode virar let f x = x, e let _ = (2 * 2) + 3 pode virar let _ = 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

 
GN⁺ 2024-11-11
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.

    • A maior parte das incompatibilidades pode ser resumida a source generators e outras ferramentas baseadas em geração de código.
      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.

    • Sinto que C# é bastante subestimado.
      É 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.
    • Fico curioso sobre quais você vê como as desvantagens do F#.
      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.

    • Compilar código F# e expô-lo como módulos TypeScript foi uma boa experiência.
      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#.
    • Fico curioso se a empresa tem alguma relação com Darklang.
      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/

    • Há duas possibilidades
      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
    • Npgsql é um driver popular de C# e também tem um wrapper para F#
      Eu começaria por aí
    • Como desenvolvedor que usa várias linguagens, às vezes tenho a oportunidade de usar F#, e faço a maior parte das provas de conceito no REPL
      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 instalado
      Em 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

    • .NET e C# passaram, mais recentemente, a ter releases anuais subindo um número por vez, e parece que F# está seguindo o mesmo modelo
      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
    • Tanto em C# quanto em F#, a versão do .NET se refere à versão das ferramentas de build usadas ao executar dotnet build
      Isso 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
    • Novas versões do .NET saem todo ano, e as versões de C# e F# também são alinhadas a essas versões anuais
      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

  • Nunca usei F# diretamente, mas, quando dei uma olhada, este material pareceu excelente: https://fsharpforfunandprofit.com/

    • Para quem está conhecendo F# pela primeira vez, é realmente um dos melhores sites
      É 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 apply e bind