1 puntos por GN⁺ 2024-11-11 | 1 comentarios | Compartir por WhatsApp
  • F# 9, incluido en .NET 9, reduce los problemas de seguridad que surgen en la interoperabilidad con C#/.NET mediante tipos de referencia que admiten null y mejoras en los diagnósticos del compilador
  • Con propiedades .Is* en uniones discriminadas, patrones activos parciales que devuelven bool, computation expressions vacías y más, la sintaxis cotidiana de F# se vuelve más concisa
  • FSharp.Core agrega funciones aleatorias para colecciones y soporte para expresiones de colección de C#, lo que facilita usar colecciones inmutables de F# también desde otro código .NET
  • El compilador expone antes problemas como uso incorrecto de atributos, métodos IL por encima de 65,520 y visibilidad de miembros privados
  • Con optimizaciones en comprobaciones de equality, rangos de enteros y comprehensions de listas y arrays, algunos bucles son 1.25×~8× más rápidos, y algunas comprehensions de arrays hasta 10× más rápidas

F# 9 disponible en .NET 9

  • F# 9 incluye cambios para hacer que los programas sean más seguros, resilientes y con mejor rendimiento
  • Está disponible en .NET 9, y el SDK más reciente de .NET se puede obtener desde la página de descargas de .NET
  • Los cambios principales se desarrollan en el repositorio de código abierto de F#

Cambios en el lenguaje

  • Tipos de referencia que admiten null

    • F# está diseñado para evitar null, pero al usarlo con bibliotecas .NET escritas en C#, puede entrar null
    • F# 9 expresa de forma type-safe tipos de referencia donde null es válido, como string | null
    • Si se asigna null a string o se accede directamente a .Length desde un valor string | null, se genera una advertencia de posible null
    • Si en pattern matching se maneja primero el caso null, los bindings posteriores se tratan como valores no null
    • En código genérico, para devolver null se necesita una restricción de tipo de referencia como 'T : not struct
    • Más información en Nullable Reference Types in F# 9
  • Propiedades .Is* en uniones discriminadas

    • Las uniones discriminadas (discriminated union) tienen propiedades .Is* generadas automáticamente para cada caso
    • Por ejemplo, si el tipo Contact tiene los casos Email y Phone, se puede comprobar si es un caso específico con algo como person.contact.IsEmail
    • Antes había que escribir código como Email _ -> true | _ -> false en una expresión match para hacer la misma comprobación
  • Retorno bool en patrones activos parciales

    • Los patrones activos parciales (partial active pattern) antes tenían que devolver Some () cuando el match era exitoso y None cuando fallaba
    • En F# 9 también se permite devolver bool
    • En un ejemplo de matching de strings ignorando mayúsculas y minúsculas, se puede devolver directamente el resultado de String.Equals(..., StringComparison.OrdinalIgnoreCase)
  • Prioridad a métodos de extensión cuando hay argumentos

    • Algunas bibliotecas .NET definen métodos de extensión con el mismo nombre que una propiedad propia del tipo
    • F# 9 se adapta a este patrón: cuando se proporcionan argumentos, en lugar de fallar el type checking, resuelve el método de extensión
    • En el ejemplo, un método de extensión X(f: Foo, i: int) con el mismo nombre que la propiedad X de Foo puede llamarse como f.X(1) para configurar la propiedad y encadenar llamadas
  • Computation expressions vacías

    • F# 9 admite computation expressions vacías
    • seq { } crea una secuencia vacía, y en código como un DSL de HTML se pueden expresar bloques vacíos como p { }
    • Una computation expression vacía deriva en una llamada al método Zero del builder
    • Es una sintaxis más natural que el anterior builder { () }

Mejoras en directivas hash y F# Interactive

  • Argumentos de directivas hash que no son strings

    • Las directivas hash del compilador antes solo permitían argumentos string entre comillas
    • En F# 9 pueden recibir argumentos de tipo arbitrario
    • Se puede escribir #nowarn 0070 en lugar de #nowarn "0070", y #time on en lugar de #time "on"
  • #help ampliado en F# Interactive

    • La directiva #help de F# Interactive muestra documentación de objetos o funciones en el REPL
    • Los argumentos se pueden pasar sin comillas
    • Por ejemplo, #help List.map;; muestra descripción, parámetros, valor de retorno, ejemplos, nombre completo e información de assembly
    • Más información en Enhancing #help in F# Interactive blog post
  • #nowarn permite el prefijo FS

    • Antes, si se escribía #nowarn "FS0057", aunque el número de advertencia fuera correcto, se producía el error Invalid warning number 'FS0057'
    • En F# 9, el número de advertencia se permite aunque tenga el prefijo FS
    • Funcionan #nowarn 57, #nowarn 0057, #nowarn FS0057 y las formas string "57", "0057", "FS0057"
    • Dentro de un proyecto conviene mantener el mismo estilo

Mejoras de seguridad y diagnósticos del compilador

  • Advertencia por ubicación incorrecta de [<TailCall>]

    • F# 9 emite una advertencia si el atributo [<TailCall>] se coloca en una ubicación no válida
    • Los ejemplos incluyen casos donde se coloca en una función no recursiva, un valor de let binding o un valor de let binding recursivo
    • Estos atributos no afectan el comportamiento del código, pero pueden confundir a quien lo lee
  • Aplicación más estricta de AttributeTargets

    • El compilador aplica correctamente AttributeTargets en valores let, funciones, declaraciones de union case, constructores implícitos, structs y classes
    • Esto puede evitar bugs poco visibles, como olvidar el argumento unit en pruebas Xunit
    • Antes, [<Fact>] let ``this test always fails`` = Assert.True(false) no era una función real, por lo que el test runner lo ignoraba y dotnet test pasaba
    • Ahora se produce el error error FS0842: This attribute is not valid for use on this language element
  • Recuperación del parser

    • Las mejoras en la recuperación del parser permiten que funciones de herramientas como el resaltado de sintaxis sigan funcionando incluso con código sintácticamente incompleto durante la edición
    • Los casos cubiertos incluyen patrones as incompletos, object expressions, declaraciones de enum case, declaraciones de record, patrones complejos de primary constructor, identificadores largos no resueltos, cláusulas match vacías, y campos y tipos de campo faltantes en union cases
  • Mensajes de diagnóstico y precisión de ubicación

    • F# 9 agrega nuevos mensajes de diagnóstico y ubicaciones de diagnóstico más precisas
    • Incluye casos como métodos override ambiguos en object expressions, miembros abstractos en clases no abstractas, propiedades con el mismo nombre que casos de uniones discriminadas, desajuste en la cantidad de argumentos de active patterns, unions con campos duplicados, y uso conjunto de use! y and! en computation expressions
    • Para clases con más de 65,520 métodos en el IL generado, ahora se produce un nuevo error en tiempo de compilación
    • Esas clases no pueden cargarse en el CLR, lo que deriva en errores en tiempo de ejecución
  • Opción de visibilidad real

    • F# tiene la característica de registrar miembros privados como internal en IL, por lo que proyectos que no son F# con acceso a un proyecto F# mediante InternalsVisibleTo podían acceder indebidamente a miembros privados
    • F# 9 ofrece el flag de compilador --realsig+ como opción opt-in para corregir este comportamiento
    • En .fsproj se puede usar agregando <RealSig>true</RealSig>
    • Se puede comprobar si la solución depende del comportamiento anterior

Cambios en la biblioteca estándar FSharp.Core

  • Funciones aleatorias para colecciones

    • Se agregan funciones de muestreo aleatorio y mezcla a los módulos List, Array y Seq
    • Facilitan el uso de F# en escenarios comunes que requieren aleatoriedad, como ciencia de datos, machine learning y desarrollo de videojuegos
    • Todas las funciones tienen tres variantes
      • Una variante que usa una instancia compartida implícita y thread-safe de Random
      • Una variante que recibe una instancia de Random como argumento
      • Una variante que recibe una función randomizer personalizada que debe devolver un valor float mayor o igual a 0.0 y menor que 1.0
    • Las funciones disponibles son cuatro: Shuffle, Choice, Choices y Sample, cada una con tres variantes
    • La lista completa de funciones y variantes está en RFC #1135
  • Comportamiento por función aleatoria

    • Shuffle devuelve una nueva colección del mismo tipo y tamaño; cada elemento se mezcla con peso uniforme respecto de la longitud de la colección
    • En arrays también existe una variante InPlace que mezcla los elementos dentro del array existente
    • Choice devuelve un único elemento aleatorio con peso uniforme respecto del tamaño de la colección
    • Choices elige N elementos de la colección de entrada en orden aleatorio, y un mismo elemento puede elegirse varias veces
    • Sample elige N elementos de la colección de entrada en orden aleatorio, pero no elige el mismo elemento dos veces
    • En Sample, N no puede ser mayor que la longitud de la colección
  • Constructor sin argumentos de CustomOperationAttribute

    • Se agrega un constructor sin argumentos a CustomOperationAttribute, lo que facilita crear operaciones personalizadas en builders de computation expressions
    • En la mayoría de los casos, el nombre explícito coincide con el nombre del método, por lo que se puede usar [<CustomOperation>] en lugar de [<CustomOperation("bar")>]
  • Soporte para expresiones de colección de C#

    • Desde C# se pueden inicializar listas y sets de F# mediante expresiones de colección (collection expression)
    • Por ejemplo, en lugar de SetModule.FromArray([1, 2, 3]), se puede escribir FSharpSet<int> mySet = [ 1, 2, 3 ];
    • Las colecciones inmutables de F# se pueden usar cuando se necesita structural equality que no está presente en las colecciones de System.Collections.Immutable

Mejoras de rendimiento

  • Optimización de comprobaciones de equality

    • Las comprobaciones de equality son más rápidas y reducen las asignaciones de memoria
    • En un ejemplo que busca con Array.contains un valor inexistente en un array de tipo struct, antes se hacía boxing 1,000 veces, pero ahora no se hace boxing
    • En un benchmark de funciones de array sobre structs de 2 miembros, el tiempo promedio de ArrayContainsNonexisting bajó de 5,190.95 ns a 766.005 ns, y las asignaciones pasaron de 24,000 B a 0
    • ArrayTryFindNonexisting bajó de 5,139.58 ns a 1,140.515 ns, y las asignaciones se redujeron de 24,024 B a 24 B
    • Más información en F# Developer Stories: How we’ve finally fixed a 9-year-old performance issue
  • Compartición de campos en uniones discriminadas struct

    • Si varios casos de una unión discriminada struct tienen el mismo nombre y tipo de campo, pueden compartir la misma ubicación de memoria
    • Esto reduce el uso de memoria del struct
    • En el ejemplo, el tamaño de una unión discriminada struct que comparte campos basados en el mismo int64 es de 16 bytes
    • La versión anterior, donde había que usar un nombre de campo único por caso, era de 60 bytes
    • Antes no se permitía el mismo nombre de campo, así que no hay problemas de compatibilidad binaria
  • Optimización de rangos de enteros

    • El compilador genera código optimizado para más casos de expresiones start..finish y start..step..finish
    • Antes solo se optimizaba cuando el tipo era int/int32 y el step era la constante 1 o -1
    • Otros tipos enteros y otros valores de step usaban una implementación ineficiente basada en IEnumerable
    • Ahora todos estos casos están optimizados
    • En for … in start..finish do …, [start..step..finish] y [for n in start..finish -> f n] se observan mejoras de velocidad de 1.25× a 8×
  • Optimización de comprehensions de listas y arrays

    • Se optimiza la forma for x in xs -> … en comprehensions de listas y arrays
    • La mejora es especialmente notable en arrays
    • La velocidad mejora hasta 10×, y el tamaño de las asignaciones se reduce de la mitad a un cuarto

Mejoras en las herramientas de Visual Studio

  • Live buffers activados por defecto

    • La función live buffers de Visual Studio antes era opt-in, pero tras pruebas suficientes quedó activada por defecto
    • El compilador en segundo plano que impulsa el IDE usa buffers de archivos no guardados
    • Los cambios se aplican aunque el archivo no se guarde en disco
    • Antes, al renombrar símbolos en un archivo editado pero no guardado, podían ocurrir comportamientos inesperados
  • Code fix para eliminar paréntesis innecesarios

    • Visual Studio ofrece un code fix para eliminar paréntesis innecesarios
    • Por ejemplo, let f (x) = x puede cambiarse a let f x = x, y let _ = (2 * 2) + 3 a let _ = 2 * 2 + 3
    • Es una función para reducir casos donde los paréntesis no aportan claridad y se acercan más al ruido
  • Soporte de visualizadores personalizados en proyectos F#

    • Los visualizadores del depurador (debugger visualizer) de Visual Studio también funcionan en proyectos F#
  • Tooltip de signature en medio de pipelines

    • Antes, cuando en medio de un pipeline ya se habían aplicado parámetros curried complejos de una función, no se ofrecía signature help
    • Ahora se muestra el tooltip de signature para el siguiente parámetro

1 comentarios

 
GN⁺ 2024-11-11
Opiniones de Hacker News
  • F# ha sido mi lenguaje favorito desde que lo conocí en la universidad
    Estaba muy por delante de C# en funcionalidades como uniones, seguridad ante nulos, pattern matching, records, inferencia de tipos más potente y restricciones genéricas
    Es bueno que C# haya ido incorporando estas funcionalidades con el tiempo, pero es una lástima que lo haya hecho de formas incompatibles entre sí
    Como la inversión en F# es mucho menor que en C#, también se ha quedado atrás en ritmo de innovación, pero sigue siendo un lenguaje excelente, en general compatible con el ecosistema .NET, y permite obtener el mismo rendimiento que C# con mucho menos boilerplate

    • La mayoría de las incompatibilidades se pueden resumir en generadores de código fuente (source generators) y otras herramientas basadas en generación de código
      Se puede manejar con bastante facilidad escribiendo un proyecto auxiliar en C# que contenga el “código pegamento” necesario
      Me pregunto si tienes algún otro problema concreto en mente
      F# 9 también admite el uso de argumentos genéricos ref struct incorporados recientemente a C#, y tengo entendido que planea introducir funcionalidades definidas en el propio F#
      Hasta ahora ha hecho un trabajo impresionante poniéndose al día, y merece mucho más reconocimiento
  • Es difícil siquiera imaginar la parte que dice: “ahora hay un nuevo error en tiempo de compilación para las clases que tengan más de 65,520 métodos en el IL generado. El CLR no puede cargar esas clases, por lo que se produce un error en tiempo de ejecución”
    En cualquier caso, F# es un gran lenguaje
    Creo que es lo segundo mejor que ha lanzado Microsoft después de Excel, y convierte a .NET en una plataforma razonable

    • Siento que C# está bastante subestimado
      Es relativamente fácil de enseñar a personas que ya conocen JS o TS, y también es un lenguaje muy productivo
      Se usa en contextos muy diversos, desde motores de videojuegos y backends empresariales hasta aplicaciones de escritorio
      Creo que Microsoft cometió algunos errores al principio que frenaron su crecimiento, pero como lenguaje de propósito general es realmente bueno y relativamente fácil de aprender
    • Me da curiosidad saber cuáles consideras que son las desventajas de F#
      Hace unos años lo probé un poco con un IDE ligero llamado LINQPad, pero después no seguí de cerca sus ventajas, desventajas ni evolución
      https://www.linqpad.net/
  • En Phosphor tomamos una gran decisión, a escala de varios años, de apostar la empresa y la dirección tecnológica por F#
    Después de intentarlo durante más de un año, reescribimos por completo la aplicación en TypeScript y Rust
    El producto que estamos construyendo es una herramienta de programación para usuarios finales, así que la frontera tradicional entre frontend y backend se vuelve difusa, y el ecosistema .NET no encajaba bien ahí
    Originalmente queríamos compilar código F# con Fable a JS, Rust, .NET, etc., para mantener la seguridad de tipos entre varias tecnologías y hacer la interoperabilidad necesaria
    En la práctica, la interoperabilidad entre varias bibliotecas fue mucho más difícil de lo esperado, y administrar y actualizar múltiples dependencias y bindings fue realmente doloroso
    Sigo pensando que la premisa de que F# produce código hermoso y eficiente es cierta, pero por su ecosistema y su forma de diseño, creo que solo encaja bien en aplicaciones con una frontera tradicional clara entre frontend y backend
    En esos casos, F# terminaría usándose solo en el backend
    Las tecnologías que más me entusiasman ahora son Effect y Moonbit, que usamos internamente
    La biblioteca Schema de Effect cubre muchos huecos del sistema de tipos de TS, y Moonbit parece una versión moderna de F# sin la dependencia de MS/.NET
    Moonbit fue diseñado por el creador de ReScript, que podría considerarse el Fable para OCaml, está muy bien diseñado y compila directamente a salidas JS, WASM y nativas optimizadas
    Usamos Effect en producción y Moonbit todavía no, pero como lenguaje creado para un mundo AI-first, su potencial es bastante impresionante

    • Compilar código F# y exponerlo como módulos TypeScript fue una buena experiencia
      La lógica de negocio principal y los validadores estaban escritos en F#, mientras que el resto de la app frontend estaba en TypeScript y el backend en C#
      Es decir, manteníamos solo la lógica central y la validación en F#, y todo el input/output lo manejaban TS y C#
    • Me pregunto si la empresa tiene alguna relación con Darklang
      Recuerdo que era un producto parecido y que estaba escrito en F#, pero no he seguido su situación reciente
      Effect es bastante bueno, y ojalá existiera también para otros lenguajes además de TypeScript
      MoonBit parece un lenguaje de programación propietario propio, así que me da cierta duda migrar a eso en lugar de a un lenguaje conocido; me interesa saber cómo ves esa parte
  • Tuve una clase de criptografía en la que podíamos elegir cualquier lenguaje que usara .NET, y la tarea hecha en F# era mucho más legible que las de los demás
    Me gustaría usarlo más seguido, pero el trabajo de ciencia de datos es casi 100% Python

  • F# 9 también se beneficia de casi todas las mejoras de rendimiento del propio .NET 9
    En particular, las mejoras en análisis de escape de objetos (object escape analysis) son importantes
    https://devblogs.microsoft.com/dotnet/performance-improvemen...

  • Extraño muchísimo la época en que trabajaba con F#
    Es un lenguaje muy productivo, así que disfruto con solo seguir sus actualizaciones
    Teniendo en cuenta el tamaño de la comunidad y la indiferencia que Microsoft muestra a veces, creo que el soporte de herramientas era bastante bueno
    La mayor molestia era la precisión de la cobertura de pruebas de código

  • Hace poco estuve probando un poco F# y, viniendo de Python, me gustó mucho poder experimentar con distintas cosas en el REPL
    Me da curiosidad si los desarrolladores experimentados de F# también lo usan así
    Este invierno quiero hacer un pequeño proyecto de backend web para conocer mejor el lenguaje y el ecosistema
    Escuché que Oxpecker es bueno para la parte HTTP, pero me pregunto si hay recomendaciones de clientes o drivers para PostgreSQL
    No me gustan los ORM
    https://lanayx.github.io/Oxpecker/

    • Hay dos posibilidades
      https://monazita.gitlab.io/monazita/ fue creado para proyectos personales mientras se aprendía F#; básicamente funciona, pero todavía tiene margen para pulirse más y es exclusivo para PostgreSQL
      https://github.com/jacentino/DbFun está más pulido que el proyecto anterior y soporta varias bases de datos
    • Npgsql es un driver popular de C# y también tiene wrappers para F#
      Sería buena idea empezar por ahí
    • Como desarrollador que usa varios lenguajes, a veces tengo oportunidad de usar F#, y la mayoría de las pruebas de concepto las hago con el REPL
      Si no estoy seguro de una API, incluso dentro de una base de código real presiono “Send to F# interactive” para ejecutarla y experimentar dentro de un módulo
      También lo uso para probar bibliotecas nuevas, hacer benchmarks rápidos o como reemplazo de scripts de PowerShell
      Con el shebang #!/usr/bin/env -S dotnet fsi se pueden hacer ejecutables los scripts de F#, así que lo uso a menudo como alternativa a bash/Python para scripts alrededor de proyectos .NET donde ya está instalado dotnet-sdk
      Por lo general se ejecuta más rápido y suele ser igual de conciso
      En lo personal, siento que la sintaxis y los modismos de F# encajan mejor que C# con la programación en REPL, uniendo pequeños fragmentos de código
      C# normalmente requiere más estructura orientada a objetos
  • Me pregunto cómo funciona el versionado de F#
    Hay muchas mejoras de calidad de vida que se ven bien, pero desde la perspectiva del versionado semántico no parece haber rupturas de compatibilidad que justifiquen un cambio de versión mayor, y tampoco parece un salto tan grande en características del lenguaje como para que un proyecto que no usa versionado semántico pase de 8 a 9
    En otro comentario mencionaron el reciente .NET 9, así que me pregunto si es para alinearse con la numeración de versiones de .NET

    • .NET y C# pasaron desde hace un tiempo a lanzamientos que suben un dígito cada año, y parece que F# sigue el mismo esquema
      No sé si el cambio se hizo deliberadamente para coincidir con la versión de .NET o si fue casualidad
      Por ejemplo, C# sacó C# 13 junto con .NET 9
    • Tanto en C# como en F#, la versión de .NET se refiere a la versión de las herramientas de compilación que se usan al ejecutar dotnet build
      Esto incluye msbuild, el compilador, paquetes NuGet, etc.
      Por ejemplo, el equipo de F# podría haber publicado cambios del lenguaje entre 8 y 9, pero aunque no lo hubiera hecho, podría haber lanzado cambios del compilador o de msbuild que por alguna razón requirieran .NET 9
      Los desarrolladores normalmente solo tienen que subir su código a la versión más reciente del runtime, salvo que necesiten esperar a que el entorno de despliegue tenga instalado el .NET más nuevo
      Hoy eso también es menos necesario gracias a cambios de MSBuild para crear despliegues autocontenidos, pero es algo que conviene saber
    • Las nuevas versiones de .NET salen cada año y las versiones de C# y F# se alinean con esa versión anual
      Solo que C# va 4 números por delante
      De todos modos, creo que el versionado semántico está sobrevalorado
  • Me pregunto cuál es el estado de F# como alternativa a C# al crear apps GUI en Windows
    También me da curiosidad si hay empresas que usen F# para ese fin

  • Nunca he probado F# directamente, pero cuando lo estuve revisando, este recurso me pareció excelente: https://fsharpforfunandprofit.com/

    • Para alguien que se acerca a F# por primera vez, en efecto es uno de los mejores sitios
      Es bueno tanto para desarrolladores experimentados de C# como para gente relativamente nueva en programación
      Hoy casi no se actualiza, así que es difícil encontrar discusiones sobre las novedades de F# 9, pero los artículos existentes son excelentes para entender conceptos como apply y bind