- 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 devuelvenbool, 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 entrarnull - F# 9 expresa de forma type-safe tipos de referencia donde null es válido, como
string | null - Si se asigna
nullastringo se accede directamente a.Lengthdesde un valorstring | 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
nullse necesita una restricción de tipo de referencia como'T : not struct - Más información en Nullable Reference Types in F# 9
- F# está diseñado para evitar
-
Propiedades
.Is*en uniones discriminadas- Las uniones discriminadas (discriminated union) tienen propiedades
.Is*generadas automáticamente para cada caso - Por ejemplo, si el tipo
Contacttiene los casosEmailyPhone, se puede comprobar si es un caso específico con algo comoperson.contact.IsEmail - Antes había que escribir código como
Email _ -> true | _ -> falseen una expresiónmatchpara hacer la misma comprobación
- Las uniones discriminadas (discriminated union) tienen propiedades
-
Retorno
boolen patrones activos parciales- Los patrones activos parciales (partial active pattern) antes tenían que devolver
Some ()cuando el match era exitoso yNonecuando 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)
- Los patrones activos parciales (partial active pattern) antes tenían que devolver
-
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 propiedadXdeFoopuede llamarse comof.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 comop { }- Una computation expression vacía deriva en una llamada al método
Zerodel 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 0070en lugar de#nowarn "0070", y#time onen lugar de#time "on"
-
#helpampliado en F# Interactive- La directiva
#helpde 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
- La directiva
-
#nowarnpermite el prefijoFS- Antes, si se escribía
#nowarn "FS0057", aunque el número de advertencia fuera correcto, se producía el errorInvalid warning number 'FS0057' - En F# 9, el número de advertencia se permite aunque tenga el prefijo
FS - Funcionan
#nowarn 57,#nowarn 0057,#nowarn FS0057y las formas string"57","0057","FS0057" - Dentro de un proyecto conviene mantener el mismo estilo
- Antes, si se escribía
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
- F# 9 emite una advertencia si el atributo
-
Aplicación más estricta de
AttributeTargets- El compilador aplica correctamente
AttributeTargetsen 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 ydotnet testpasaba - Ahora se produce el error
error FS0842: This attribute is not valid for use on this language element
- El compilador aplica correctamente
-
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
asincompletos, object expressions, declaraciones de enum case, declaraciones de record, patrones complejos de primary constructor, identificadores largos no resueltos, cláusulasmatchvací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!yand!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
InternalsVisibleTopodían acceder indebidamente a miembros privados - F# 9 ofrece el flag de compilador
--realsig+como opción opt-in para corregir este comportamiento - En
.fsprojse puede usar agregando<RealSig>true</RealSig> - Se puede comprobar si la solución depende del comportamiento anterior
- 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
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,ArrayySeq - 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
Randomcomo argumento - Una variante que recibe una función
randomizerpersonalizada que debe devolver un valorfloatmayor o igual a 0.0 y menor que 1.0
- Una variante que usa una instancia compartida implícita y thread-safe de
- Las funciones disponibles son cuatro:
Shuffle,Choice,ChoicesySample, cada una con tres variantes - La lista completa de funciones y variantes está en RFC #1135
- Se agregan funciones de muestreo aleatorio y mezcla a los módulos
-
Comportamiento por función aleatoria
Shuffledevuelve 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
InPlaceque mezcla los elementos dentro del array existente Choicedevuelve un único elemento aleatorio con peso uniforme respecto del tamaño de la colecciónChoiceselige N elementos de la colección de entrada en orden aleatorio, y un mismo elemento puede elegirse varias vecesSampleelige 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")>]
- Se agrega un constructor sin argumentos a
-
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 escribirFSharpSet<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.containsun 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
ArrayContainsNonexistingbajó de 5,190.95 ns a 766.005 ns, y las asignaciones pasaron de 24,000 B a 0 ArrayTryFindNonexistingbajó 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
int64es 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..finishystart..step..finish - Antes solo se optimizaba cuando el tipo era
int/int32y el step era la constante1o-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×
- El compilador genera código optimizado para más casos de expresiones
-
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
- Se optimiza la forma
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) = xpuede cambiarse alet f x = x, ylet _ = (2 * 2) + 3alet _ = 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
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
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 structincorporados 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
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
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
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#
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/
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
Sería buena idea empezar por ahí
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 fsise 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-sdkPor 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
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
dotnet buildEsto 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
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
https://github.com/fsprojects/Avalonia.FuncUI
https://fabulous.dev/ apunta a Avalonia/MAUI/Xamarin
https://github.com/kekyo/epoxy soporta Avalonia y WPF
Si te interesa saber si se usa así en empresas, puedo preguntar por ahí
Nunca he probado F# directamente, pero cuando lo estuve revisando, este recurso me pareció excelente: https://fsharpforfunandprofit.com/
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
applyybind