Compartir a través de


Novedades de F# 10

F# 10 le ofrece varias mejoras en el lenguaje F#, la biblioteca FSharp.Core y las herramientas. Esta versión es una versión de refinamiento centrada en la claridad, la coherencia y el rendimiento, con pequeñas mejoras pero significativas que hacen que el código diario sea más legible y sólido. F# 10 se incluye con .NET 10 y Visual Studio 2026.

Puede descargar el SDK de .NET más reciente desde la página de descargas de .NET.

Comienza

F# 10 está disponible en todas las distribuciones de .NET Core y las herramientas de Visual Studio. Para obtener más información, consulte Introducción a F#.

Supresión de advertencias delimitada

Ahora puede suprimir advertencias en secciones específicas del código mediante la nueva #warnon directiva. Esto se empareja con la directiva existente #nowarn para ofrecerle un control preciso sobre qué advertencias se aplican.

Anteriormente, al usar #nowarn, deshabilitaría una advertencia para el resto del archivo, lo que podría suprimir problemas legítimos en otras partes del archivo. Echemos un vistazo a un ejemplo 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

Si agrega #nowarn 25 encima de la definición de función, deshabilita FS0025 para todo el resto del archivo.

Con F# 10, ahora puede marcar la sección exacta donde desea que se suprima la advertencia:

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

Por el contrario, si una advertencia está deshabilitada globalmente (por ejemplo, a través de una marca del compilador), puede habilitarla localmente con #warnon. Esta directiva se aplicará hasta que se alcance un #nowarn coincidente o hasta el final del archivo.

Notas de compatibilidad importantes:

Esta característica incluye varios cambios que mejoran la coherencia de #nowarn/#warnon las directivas. Estos son cambios importantes:

  • El compilador ya no permite directivas de advertencia multilínea y vacías.
  • El compilador ya no permite espacios en blanco entre # y nowarn.
  • No se pueden usar cadenas entre comillas triples, interpoladas ni literales para números de advertencia.

El comportamiento del script también ha cambiado. Anteriormente, al agregar una #nowarn directiva en cualquier lugar de un script, se aplicaba a toda la compilación. Ahora, su comportamiento en los scripts coincide con el de los archivos .fs, aplicándose solo hasta el final del archivo o un correspondiente #warnon.

Esta característica implementa RFC FS-1146.

Modificadores de acceso en accesores de propiedades automáticas

Un patrón común en la programación orientada a objetos es crear un estado legible públicamente pero mutable de forma privada. Antes de F# 10, necesitaba sintaxis de propiedad explícita con campos de respaldo (variables ocultas que almacenan los valores de propiedad reales) para lograr esto, lo que agregó 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

Con F# 10, ahora puede aplicar diferentes modificadores de acceso a los accesores de propiedades individuales. Esto le permite especificar diferentes niveles de acceso para el get y el set de una propiedad, haciendo el patrón mucho más sencillo.

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

Puede colocar un modificador de acceso antes del nombre de propiedad (aplicando a ambos descriptores de acceso) o antes de los descriptores de acceso individuales, pero no ambos simultáneamente.

Tenga en cuenta que esta característica no se extiende a los archivos de firma (.fsi). La firma correcta del Ledger ejemplo anterior es:

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

Esta característica implementa RFC FS-1141.

Parámetros opcionales de ValueOption

Ahora puede usar una representación basada en ValueOption<'T> estructuras para parámetros opcionales. Cuando se aplica el [<Struct>] atributo a un parámetro opcional, el compilador usa ValueOption<'T> en lugar del tipo basado en option referencia. Esto evita asignaciones de montón (memoria asignada en el montón administrado que requiere recolección de basura) para el envoltorio de opciones, lo que resulta beneficioso en el código crítico para el rendimiento.

Anteriormente, F# siempre usaba el tipo asignado en el montón option para parámetros opcionales, incluso cuando el parámetro no estaba presente.

// 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"

En F# 10, puede utilizar el atributo [<Struct>] para aprovechar la estructura respaldada ValueOption.

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

Esto elimina las asignaciones en el montón cuando el argumento está ausente, lo cual es beneficioso en el código crítico para el rendimiento.

Elija esta opción basada en estructuras para valores pequeños o tipos construidos con frecuencia donde importa la presión de asignación. Use option basado en referencia predeterminada cuando dependa de asistentes para la coincidencia de patrones existentes, necesite semántica de referencia o cuando la diferencia de rendimiento sea insignificante. Esta característica refuerza la paridad con otras construcciones de lenguaje F# que ya admiten ValueOption.

Soporte para llamadas de cola en expresiones de cálculo

F# 10 agrega optimizaciones de llamadas de cola para expresiones de cálculo. Los generadores de expresiones de cálculo ahora pueden optar por estas optimizaciones mediante la implementación de métodos especiales.

Cuando el compilador traduce expresiones de cálculo en código F# normal (un proceso denominado desugaring), reconoce cuando una expresión como return!, yield!o do! aparece en una posición de cola. Si el generador proporciona los métodos siguientes, el compilador enruta esas llamadas a puntos de entrada optimizados:

  • ReturnFromFinal - se requiere una cola return! (recurre a ReturnFrom si está ausente)
  • YieldFromFinal - se invoca para un apéndice yield! (utiliza YieldFrom en caso de estar ausente)
  • Para un terminal do!, el compilador prefiere ReturnFromFinal, después YieldFromFinal, antes de volver a la ruta normal Bind

Estos *Final miembros son opcionales y existen exclusivamente para habilitar la optimización. Los constructores que no proporcionan estos miembros mantienen su semántica existente sin cambios.

Por ejemplo:

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

Sin embargo, en una posición que no sea de cola:

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

Nota de compatibilidad importante:

Este cambio puede provocar una ruptura si un constructor de expresiones de cálculo ya define miembros con estos nombres. En la mayoría de los casos, los generadores existentes siguen funcionando sin modificaciones cuando se compilan con F# 10. Los compiladores más antiguos omitirán los nuevos *Final métodos, por lo que los generadores que deben seguir siendo compatibles con versiones anteriores del compilador no deben suponer que el compilador invocará estos métodos.

Esta característica implementa RFC FS-1330.

Enlaces con tipo en expresiones de cálculo sin paréntesis

F# 10 elimina la necesidad de paréntesis al agregar anotaciones de tipo a las asignaciones de expresiones de computación. Ahora puede agregar anotaciones de tipo en los enlaces let!, use!, y and! con la misma sintaxis que los enlaces normales let.

Anteriormente, tenías que usar paréntesis para las anotaciones de tipo:

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

En F# 10, puede escribir anotaciones de tipo sin paréntesis:

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

Permitir _ en use! vinculaciones

Ahora puede usar el patrón de descarte (_) en las vinculaciones use! de expresiones de cálculo. Esto alinea el comportamiento de use! con enlaces normales use .

Anteriormente, el compilador rechazó el patrón de descarte en use! bindings, lo que le obligó a crear identificadores desechables:

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

En F# 10, puede usar el patrón de descarte directamente:

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

Esto aclara la intención al enlazar recursos asíncronos cuyos valores solo son necesarios para la gestión de ciclo de vida.

Rechazando módulos pseudo-anidados en tipos

El compilador ahora genera un error al colocar una declaración module con la misma sangría al nivel estructural dentro de una definición de tipo. Esto refuerza la validación estructural para rechazar la colocación engañosa de módulos dentro de los tipos.

Anteriormente, el compilador aceptó module declaraciones con sangría dentro de las definiciones de tipo, pero realmente creó estos módulos como elementos relacionados con el tipo en lugar de anidarlos dentro de él:

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

Con F# 10, este patrón genera el error FS0058, lo que le obliga a aclarar la intención con la ubicación adecuada del módulo:

type U =
    | A
    | B

module M =
    let f () = ()

Advertencia de obsolescencia por la omisión de seq

El compilador ahora le advierte sobre las expresiones de secuencia básicas que omiten el seq builder. Cuando use llaves de rango como { 1..10 }, verá una advertencia de desaprobación que le anima a usar la forma explícita seq { ... }.

Históricamente, F# permitía una sintaxis especial "sequence comprehension lite" donde se podía omitir la seq palabra clave:

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

En F# 10, el compilador advierte sobre este patrón y fomenta la forma explícita:

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

Actualmente se trata de una advertencia, no un error, lo que le da tiempo para actualizar el código base. Si desea suprimir esta advertencia, use la propiedad en el NoWarn archivo o #nowarn directiva del proyecto localmente y pásela el número de advertencia: 3873.

El formulario explícito seq mejora la claridad y la coherencia del código con otras expresiones de cálculo. Las versiones futuras de F# pueden producir este error, por lo que se recomienda adoptar la sintaxis explícita al actualizar el código.

Esta característica implementa RFC FS-1033.

Aplicación al objetivo del atributo

F# 10 aplica la validación de destino del atributo en todas las construcciones de lenguaje. El compilador ahora valida que los atributos solo se apliquen a sus destinos previstos comprobando AttributeTargets en los valores enlazados a let, funciones, casos de unión, constructores implícitos, estructuras y clases.

Anteriormente, el compilador permitía silenciosamente aplicar de forma incorrecta atributos a destinos incompatibles. Esto provocó errores sutiles, como que se ignoraran los atributos de prueba cuando olvidabas () definir una función.

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

En F# 10, el compilador aplica destinos de atributo y genera una advertencia cuando los atributos se aplican de forma incorrecta:

[<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)

Nota de compatibilidad importante:

Se trata de un cambio importante que puede revelar problemas anteriormente silenciosos en el código base. Los errores tempranos impiden problemas de detección de pruebas y garantizan que los atributos como analizadores y decoradores surtan efecto según lo previsto.

Compatibilidad con and! en expresiones de tarea

Ahora puede esperar varias tareas simultáneamente mediante and! en expresiones de tarea. El uso task de es una manera popular de trabajar con flujos de trabajo asincrónicos en F#, especialmente cuando se necesita interoperabilidad con C#. Sin embargo, hasta ahora no había ninguna manera concisa de esperar varias tareas simultáneamente en una expresión de cálculo.

Quizás haya empezado con código que esperaba cálculos secuencialmente:

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

Si después desea cambiarla para esperarlas simultáneamente, normalmente usaría 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
}

En F# 10, puede usar and! para un enfoque más idiomático:

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

Esto combina la semántica de la versión simultánea con la simplicidad de la versión secuencial.

Esta característica implementa la sugerencia de lenguaje F# 1363 y se implementa como una adición a la FSharp.Core biblioteca. La mayoría de los proyectos obtienen automáticamente la versión más reciente del FSharp.Core compilador, a menos que anclen explícitamente una versión. En ese caso, deberá actualizarla para usar esta característica.

Mejor recorte por defecto

F# 10 elimina una fricción de larga data al optimizar los ensamblados de F#. El recorte es el proceso de quitar código sin usar de la aplicación publicada para reducir su tamaño. Ya no necesita gestionar manualmente un archivo ILLink.Substitutions.xml solo para eliminar grandes blobs de metadatos de recursos de F# (los datos de firma y optimización que utiliza el compilador, pero que la aplicación no necesita en tiempo de ejecución).

Al publicar con el recorte activado (PublishTrimmed=true), la compilación de F# ahora genera automáticamente un archivo de sustituciones incrustado que apunta a estos recursos de F# exclusivamente para herramientas.

Anteriormente, tenía que modificar manualmente este archivo para eliminar los metadatos. Esto agregó la carga de mantenimiento y fue fácil de olvidar.

El resultado es una salida más pequeña de forma predeterminada, menos código repetitivo que mantener y uno menos peligro de mantenimiento. Si necesita un control manual completo, puede agregar su propio archivo de sustituciones. Puede desactivar la generación automática con la <DisableILLinkSubstitutions>false</DisableILLinkSubstitutions> propiedad .

Compilación paralela en versión preliminar

Una actualización emocionante para los usuarios de F# que buscan reducir los tiempos de compilación: las características de compilación paralelas están estabilizando. A partir de .NET 10, tres características: la comprobación de tipos basada en grafos, la generación de código IL paralelo y la optimización paralela se agrupan en la propiedad del ParallelCompilation proyecto.

F# 10 habilita esta configuración de forma predeterminada para los proyectos que usan LangVersion=Preview. Tenemos previsto habilitarlo para todos los proyectos de .NET 11.

Asegúrese de probarlo y ver si acelera la compilación. Para habilitar la compilación en paralelo en F# 10:

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

Si desea optar por no participar mientras sigue disfrutando de otras características en versión preliminar, establezca en ParallelCompilation false:

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

La compilación en paralelo puede reducir significativamente los tiempos de compilación de los proyectos con varios archivos y dependencias.

Caché de subsumir tipos

El compilador ahora almacena en caché las comprobaciones de relaciones de tipo para acelerar la inferencia de tipos y mejorar el rendimiento del IDE, especialmente cuando se trabaja con jerarquías de tipos complejas. Al almacenar y reutilizar los resultados de las comprobaciones de subsumpción anteriores, el compilador evita cálculos redundantes que ralentizaron anteriormente la compilación e IntelliSense.

Administración de la memoria caché:

En la mayoría de los casos, la caché de subsumción de tipos mejora el rendimiento sin ninguna configuración. Sin embargo, si experimenta un aumento de la superficie de memoria o un mayor uso de cpu (debido a los trabajos de mantenimiento de caché), puede ajustar el comportamiento de la memoria caché:

  • Para deshabilitar la memoria caché por completo, establezca <LangVersion>9</LangVersion> en el archivo del proyecto para revertir al comportamiento de F# 9.
  • Para desactivar la expulsión asincrónica de caché (lo que aumenta la presión de subproceso) y usar la expulsión sincrónica en su lugar, establezca la variable de entorno FSharp_CacheEvictionImmediate=1.