Nota
L'accés a aquesta pàgina requereix autorització. Podeu provar d'iniciar la sessió o de canviar els directoris.
L'accés a aquesta pàgina requereix autorització. Podeu provar de canviar els directoris.
Le damos la bienvenida a F# 11 Esta versión le permite mantener diferenciados los modelos de dominio, reutilizar algoritmos genéricos, coordinar operaciones asincrónicas y reducir el uso de memoria del compilador.
- Las propagaciones de registros reutilizan campos sin obligar a los modelos de almacenamiento y de aplicación a compartir un tipo.
- Los miembros de extensión en algoritmos genéricos permiten que los tipos existentes compartan operaciones sin necesidad de utilizar contenedores.
- La integración de .NET conserva los metadatos delegados y reutiliza la documentación de la API.
- Las métodos auxiliares para operaciones asincrónicas y tareas propagan la cancelación entre distintos límites de API y limitan el número de operaciones simultáneas.
- Las mejoras del compilador y las bibliotecas reducen los análisis repetidos y evitan algunas asignaciones.
Gracias a la comunidad de F# por las propuestas, implementaciones, revisiones y pruebas que han hecho posibles estos cambios. Las siguientes características incluyen vínculos a colaboradores.
Note
Los ejemplos requieren un compilador de F# 11 y FSharp.Core 11 con las características que se muestran. En el SDK de .NET 11.0.100, las características de F# 11.2 requieren <LangVersion>preview</LangVersion>; consulte la configuración de idioma y la compatibilidad planeada del SDK. El ejemplo en tiempo de ejecución asincrónico tiene requisitos adicionales de tiempo de ejecución; es posible que un SDK de .NET 11 anterior no contenga todos los cambios.
Propagación de registros: modelos distintos sin asignaciones repetitivas
Para mantener diferenciados los modelos de almacenamiento y de aplicación, las propagaciones de registros permiten reutilizar los campos que comparten sin necesidad de declararlos repetidamente. Aquí los modelos comparten los detalles del cliente, pero usan tipos de identificador diferentes:
open System
type CustomerId = CustomerId of Guid
type CustomerDetails = { Name: string; PreferredLanguage: string }
type CustomerRow =
{ ...CustomerDetails
Id: string
InternalNote: string }
type Customer =
{ ...CustomerDetails
Id: CustomerId }
...CustomerDetails contribuye a las declaraciones de campos. En el límite, ...row copia los valores mientras la conversión analiza el identificador:
let toCustomer (row: CustomerRow) =
match Guid.TryParse row.Id with
| true, id -> Ok ({ ...row; Id = CustomerId id }: Customer)
| false, _ -> Error "Invalid customer identifier."
El destino mantiene los detalles compartidos, reemplaza Id y excluye InternalNote.
Guid.TryParse, no la propagación, valida el identificador; La entrada con formato incorrecto genera Error.
Esto resuelve el problema de asignación de la capa de aplicación. Las propagaciones de valores también permiten combinar registros con nombre y anónimos, mientras que las propagaciones de tipos solo permiten crear registros con nombre. Los campos posteriores tienen prioridad y los orígenes se evalúan una sola vez, en orden. Consulte Operadores de propagación de registros para ver ejemplos de composición y riesgos de evolución compartida.
Gracias a @brianrourkeboll por las propagaciones de registros.
Miembros de extensión para algoritmos genéricos, operadores y tuplas
Para compartir un algoritmo entre los tipos existentes, F# 11.2 permite a los miembros de extensión satisfacer las restricciones de parámetros de tipo (SRTP) resueltas estáticamente. Por ejemplo, una transformación puede gestionar listas y opciones sin tipos de contenedor.
Note
En el SDK de .NET 11.0.100, habilite <LangVersion>preview</LangVersion> dónde se resuelven las extensiones y abra el módulo correspondiente. Consulte los requisitos de la biblioteca.
Compartir una transformación entre listas y opciones
Con map de la biblioteca en el ámbito, la aplicación puede limpiar cualquiera de las dos representaciones, cambiar los tipos de los elementos y componer transformaciones:
let inline cleanNames values =
map (fun (name: string) -> name.Trim().ToUpperInvariant()) values
let batch: string list = cleanNames [ " Ada "; " grace " ]
let optional: string option = cleanNames (Some " linus ")
let missing: string option = cleanNames (None: string option)
let lengths: int list = map String.length batch
let nested: int option list = map (map String.length) [ Some "abc"; None ]
printfn "%A; %A; %A" batch optional missing
printfn "%A; %A" lengths nested
// ["ADA"; "GRACE"]; Some "LINUS"; None
// [3; 5]; [Some 3; None]
Los adaptadores de Structural.Adapters usan operaciones habituales, en lugar de imponer contenedores nuevos:
type List<'T> with
static member Map(source: 'T list, mapping: 'T -> 'U) : 'U list =
List.map mapping source
type Option<'T> with
static member Map(source: 'T option, mapping: 'T -> 'U) : 'U option =
Option.map mapping source
La función compartida en Structural.Algorithms solicita al tipo de entrada un miembro Map estático:
let inline map (mapping: 'T -> 'U) (source: ^Input) : ^Output =
(^Input :
(static member Map: ^Input * ('T -> 'U) -> ^Output)
(source, mapping))
^Input y ^Output son tipos resueltos estáticamente. En cada llamada concreta, el compilador selecciona el miembro con la firma necesaria. Las extensiones no añaden la implementación de una interfaz. La aplicación completa abre ambos módulos.
Operadores, contexto y tipos de salida
El mismo mecanismo admite operadores de tupla y propagación de contexto y construcción orientada por la salida. Los encabezados de tupla nativos necesitan la característica F# 11.2 habilitada en sus declaraciones, así como en los sitios de llamadas de resolución de extensiones.
Una adición independiente, AllowOverloadOnReturnType, permite que una fábrica declare sobrecargas distinguidas por sus tipos de retorno:
type Inject =
[<AllowOverloadOnReturnType>]
static member Pure(value: 'T) : 'T list = [value]
[<AllowOverloadOnReturnType>]
static member Pure(value: 'T) : 'T option = Some value
let batch: string list = Inject.Pure "ready"
let optional: string option = Inject.Pure "ready"
// ["ready"] and Some "ready"
Este atributo Core 11 no requiere versión preliminar. La anotación de resultado selecciona una sobrecarga; sin ella, la llamada sigue siendo ambigua. Consulte Selección por tipo de valor devuelto.
Gracias a @gusty por ampliar la compatibilidad de programación genérica.
Integración de .NET y la experiencia de desarrollo
Los delegados directos conservan el contrato real del controlador
Para exponer los nombres de los controladores y los atributos de enlace a los marcos de trabajo, F# 11 permite hacer referencia directamente a una función con nombre compatible en lugar de utilizar un contenedor. API mínimas de ASP.NET Core pueden entonces interpretar el contrato de rutas, servicios y encabezados::
open System
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Mvc
open Microsoft.Extensions.DependencyInjection
type ShipmentStore() =
member _.Status trackingId =
if trackingId = "SHP-1042" then "Dispatched" else "Unknown"
let status
(trackingId: string)
([<FromServices>] store: ShipmentStore)
([<FromHeader(Name = "X-Customer")>] customer: string) =
$"{customer}: {trackingId} is {store.Status trackingId}"
let handler = Func<string, ShipmentStore, string, string>(status)
Consulte el proyecto ejecutable y las reglas de construcción y compatibilidad. Las capturas y la adaptación de argumentos todavía pueden requerir envoltorios.
Gracias a @kerams por los delegados directos.
Reutilizar documentación XML
Para mantener los comentarios compartidos de la API en un único lugar, inclúyalos en shared.xml:
<docs>
<tracking>
<remarks>An unrecognized tracking identifier has status Unknown.</remarks>
</tracking>
</docs>
Con GenerateDocumentationFile activado, F# 11 expande <include> en las entradas de miembro generadas:
/// <summary>Find the latest status of a shipment.</summary>
/// <include file="shared.xml" path="/docs/tracking/*"/>
let status trackingId =
if trackingId = "SHP-1042" then "Dispatched" else "Unknown"
/// <summary>Find the latest statuses of several shipments.</summary>
/// <include file="shared.xml" path="/docs/tracking/*"/>
let statuses trackingIds =
trackingIds |> List.map (fun id -> id, status id)
La implementación de include actualiza ambas descripciones a partir de un único fragmento. Por otra parte, el solucionador de herramientas expone las descripciones de base para <inheritdoc/>; el compilador conserva esa etiqueta para los consumidores de la documentación. Consulte la documentación XML para ver ejemplos y reglas de resolución.
Gracias a @T-Gro por la documentación XML compartida en FSharp.Core.
Depurar funciones en línea en compilaciones no optimizadas
Para inspeccionar los argumentos de las funciones auxiliares insertadas, establezca un punto de interrupción en let total en una compilación no optimizada y entre en addFee. F# 11 ahora prefiere métodos que se puedan invocar en estas compilaciones:
let inline addFee (fee: decimal) (amount: decimal) =
let total = amount + fee
total
let invoiceTotal = addFee 2m 40m
printfn "%M" invoiceTotal
// 42
Las compilaciones optimizadas conservan el inlining. Algunas construcciones deben seguir expandiéndose. Consulte Depurar funciones insertadas para obtener información sobre estas limitaciones y la opción --always-inline.
Asincronía y coordinación de tareas
Para conectar flujos de trabajo de F# con API basadas en tareas, FSharp.Core 11 agrega puentes compatibles con cancelación, lotes limitados y transformaciones de resultados.
Limite las operaciones de E/S pendientes, no solo la colección de resultados
Un lote grande de operaciones de lectura puede iniciar más trabajo del que una aplicación debería tener pendiente. Para decidir cuándo comienza el trabajo basado en tareas, un coordinador necesita fábricas, no tareas que ya se hayan iniciado:
open System.IO
open System.Threading
let readFiles (paths: seq<string>) (ct: CancellationToken) =
paths
|> Seq.map (fun path ->
fun token -> File.ReadAllTextAsync(path, token))
|> Task.parallelLimit 4 ct
Como máximo, cuatro lecturas se realizan de forma concurrente; los resultados mantienen el orden de entrada. Un error solicita la cancelación de las tareas relacionadas mediante los tokens proporcionados. Esto limita la simultaneidad, no las solicitudes por segundo. Consulte Funciones asincrónicas y de tareas para obtener información sobre las variantes secuenciales y las que devuelven un resultado de tipo unit.
Conserve la cancelación y la excepción subyacente al atravesar el límite
Cuando un flujo de trabajo inicia una operación externa, su token debe propagarse junto con esa operación.
Async.StartTaskImmediate pasa el token del flujo de trabajo en ejecución a la factoría de tareas y espera a que finalice:
open System.IO
let tryReadFile path =
async {
try
let! contents =
Async.StartTaskImmediate(fun ct -> File.ReadAllTextAsync(path, ct))
return Some contents
with :? FileNotFoundException ->
return None
}
Un error en una sola tarea llega a este controlador como la excepción FileNotFoundException subyacente. Para una tarea o tarea de valor ya creada, utilice Async.Await; el Async.AwaitTask anterior conserva su comportamiento de encapsulación de excepciones.
Para los autores de llamadas basadas en tareas, Task.startAsyncImmediate token workflow proporciona la conversión inversa. Se inicia en el subproceso de llamada y continúa ejecutándose en él hasta la primera suspensión asincrónica.
Usar métodos auxiliares correspondientes para transformaciones breves
Para transformar un único resultado sin necesidad de utilizar una expresión de cálculo completa, utilice los métodos auxiliares correspondientes de Microsoft.FSharp.Control. Por ejemplo, lea un archivo, obtenga su longitud y devuelva un error normal como Error:
open System.IO
open System.Threading
let fileLength (path: string) (ct: CancellationToken) =
File.ReadAllTextAsync(path, ct)
|> Task.map String.length
|> Task.catch
Un archivo que contiene Report 1 proporciona Ok 8; un archivo que falta proporciona un Error objeto que contiene FileNotFoundException. La cancelación sigue propagándose.
Async y ValueTask ofrecen métodos auxiliares equivalentes para map, bind, y el tratamiento de errores. Consulte Funciones asincrónicas y de tareas. Utilice una expresión de cálculo para ramas o varios valores dependientes.
Los métodos auxiliares de ValueTask requieren la compilación netstandard2.1 o net10.0 de Core y un único consumo.
Gracias a @bartelink por sus contribuciones a la API de async/task.
Volver a generar una excepción después de notificar un error de forma asincrónica
El registro operativo debe agregar información sin cambiar lo que captura la función que llama ni ocultar dónde comenzó el fallo. Los controladores de expresiones de cálculo ahora pueden utilizar reraise () incluso después de esperar a que finalice la operación asincrónica de registro:
Note
La nueva propagación de excepciones en expresiones de cálculo es una característica de F# 11.2. En el SDK de .NET 11.0.100, habilite <LangVersion>preview</LangVersion>.
open System
open System.IO
open System.Threading.Tasks
let readReport (logFailure: exn -> Task) path =
task {
try
return! File.ReadAllTextAsync(path)
with :? IOException as error ->
do! logFailure error
return reraise ()
}
Después del inicio de sesión correcto, se propaga la misma excepción y la información original de la ubicación donde se lanzó la excepción. Un error del registrador puede reemplazar la excepción original. Esto aborda la solicitud de observabilidad asíncrona sin asincronía en tiempo de ejecución. Consulte Volver a generar una excepción desde una expresión de cálculo.
Gracias a @xperiandri por contribuir a esta compatibilidad.
FSharp.Core puede compilarse en versiones modernas de .NET
Para usar API modernas sin dejar de ser compatible con versiones de destino anteriores, FSharp.Core añade net10.0 junto a netstandard2.0 y netstandard2.1. NuGet selecciona la compilación compatible. El empaquetado de herramientas actualizado también permite la liberación asincrónica basada en tareas en F# Interactive, como en proyectos compilados. Consulte Marcos de destino de FSharp.Core.
Gracias a @T-Gro por sus contribuciones a la compatibilidad de Core con los marcos de destino modernos y al empaquetado de herramientas del SDK.
Ejecución asincrónica en tiempo de ejecución para autores de generadores
Los autores de generadores pueden permitir que el entorno de ejecución suspenda y reanude los métodos marcados, en lugar de utilizar máquinas de estados generadas por el compilador. Un builder experimental definido por la biblioteca expone el mecanismo como una expresión de cálculo:
let readManifest (path: string) =
runtimeTask {
let! text = System.IO.File.ReadAllTextAsync path
return text.Length
}
Note
Este generador experimental utiliza una característica de F# 11.2 en el punto donde se define o expande su implementación insertada; en el SDK de .NET 11.0.100, habilite las características en versión preliminar. También requiere la compilación net10.0 de Core 11, las referencias correspondientes y un entorno de ejecución que admita la ejecución asincrónica en tiempo de ejecución. La combinación de entorno de ejecución y referencias verificada en este caso es .NET 11 RC1. Usar solo .NET 10 como destino no es suficiente. Consulte Experimentos asíncronos del entorno de ejecución.
FSharp.Core no incluye este ejemplo como constructor de propósito general. El código convencional de task y async no cambia automáticamente a este mecanismo, y los autores de llamadas que utilizan una API convencional ya compilada que devuelve tareas no necesitan necesariamente habilitar las características en versión preliminar.
Gracias a @majocha por el async en tiempo de ejecución.
Texto nativo compatible con AOT y matrices bidimensionales
Para admitir AOT nativo, F# 11 quita el formato basado en reflexión y los requisitos de asignación dinámica de las operaciones de texto y matriz más comunes.
Imprimir valores y crear cadenas sin una capa de formato redundante
Si ya tiene una cadena, no necesita utilizar el formato "%s" solo para escribirla. Las nuevas funciones de Core print y printn aceptan valores mediante la conversión de F# string; printn anexa una nueva línea:
let report (processed: int) (total: decimal) (elapsed: TimeSpan) : string =
$"{processed} items, total {total:F2}, elapsed {elapsed.TotalMilliseconds:F0} ms"
printn (report 12 145.50m (TimeSpan.FromMilliseconds 250.0))
// 12 items, total 145.50, elapsed 250 ms
La interpolación de cadenas usa la conversión directa y String.Concat en más casos. Los marcadores de posición de formato .NET, como {total:F2}, utilizan un formato independiente de la referencia cultural y encapsulan el valor; %A y los especificadores de formato de printf que incluyen una precisión pueden seguir requiriendo el formato de printf. Seleccione una referencia cultural explícita para la salida localizada.
Con --reflectionfree, los registros y las uniones también pueden generar una representación de texto mediante ToString. Consulte Interpolación y AOT nativo para ver ejemplos de referencias culturales y las rutas de acceso admitidas.
Gracias a @bbatsov por las funciones de impresión y a @charlesroddie por la implementación de la interpolación.
Mantener las operaciones ordinarias de cuadrícula con índice base cero
Una cuadrícula de base cero no debe requerir un módulo Array2D de reemplazo solo para evitar una ruta de asignación de código dinámico. Las implementaciones principales actualizadas admiten la inicialización y transformación conocidas:
let grid = Array2D.init 3 4 (fun row column -> row * 4 + column)
let doubled = grid |> Array2D.map (fun value -> value * 2)
let snapshot = Array2D.copy doubled
printn $"{snapshot.GetLength(0)} x {snapshot.GetLength(1)}; last = {snapshot[2, 3]}"
// 3 x 4; last = 22
Array2D.create, init y rebase usan la ruta de asignación basada en cero en las compilaciones de Core. Los map, mapi y copy que preservan los límites pueden usar una API de asignación de .NET más reciente en la compilación net10.0; las compilaciones de .NET Standard conservan su mecanismo alternativo. Las matrices con índice inicial distinto de cero no están en desuso, pero sus restricciones nativas de asignación se mantienen.
Consulte Matrices bidimensionales y AOT nativo para obtener rutas de asignación y restricciones restantes. Compruebe los diagnósticos de publicación de la aplicación completa.
Gracias a @T-Gro por las mejoras de Array2D.
Menor retención de memoria y menos trabajo repetido
F# 11 reduce los análisis repetidos del compilador y algunas asignaciones de memoria de las aplicaciones. Estas mejoras afectan a distintas cargas de trabajo.
Compartir bibliotecas referenciadas entre proyectos
Para evitar copias repetidas de dependencias binarias compartidas, el servicio de lenguaje puede compartir grafos de bibliotecas importadas elegibles entre proyectos.
En el experimento de memoria conservada de la PR de implementación, la solución ReSharper.FSharp de diez proyectos cayó de 1547,2 MB a 1002,2 MB con el uso compartido habilitado. Se trata de una medición comparativa con esta optimización deshabilitada y habilitada para esa carga de trabajo, no de una reducción general del uso de memoria al pasar de F# 10 a F# 11. Las soluciones dominadas por referencias a proyectos en lugar de por referencias binarias compartidas pueden experimentar mejoras mucho menores.
La lectura de metadatos y la especialización de funciones insertadas también reducen las asignaciones temporales de memoria, que constituyen una métrica independiente. Consulte Rendimiento de herramientas para ver el ámbito y los controles.
Gracias a @auduchinok por las importaciones de bibliotecas compartidas.
Compilar trabajos independientes en paralelo de forma predeterminada
La compilación en paralelo ahora está habilitada de forma predeterminada, y se mantienen los resultados deterministas y las reglas de ordenación de los archivos de código fuente. Las mejoras en el almacenamiento en caché de sobrecargas y los patrones con condiciones de guarda evitan otras operaciones repetitivas. Visual Studio/FCS también reducen los análisis de documentos sin modificar y limitan las comprobaciones simultáneas; consulte Rendimiento de herramientas.
Evitar la sobrecarga de asignación de memoria de las devoluciones de llamada y los resultados vacíos
Las funciones List y Array seleccionadas ahora insertan devoluciones de llamada. Para sus propios métodos auxiliares insertados, OptimizeClosureIfNotInlined adapta una sola vez una devolución de llamada currificada opaca:
let inline foldItems
([<InlineIfLambda; OptimizeClosureIfNotInlined>] folder)
state
(items: 'T[]) =
let mutable total = state
for item in items do
total <- folder total item
total
Pueden seguir produciéndose otras asignaciones de memoria. Consulte Optimizar devoluciones de llamada currificadas para conocer los requisitos.
Los resultados vacíos son otro caso habitual de operaciones completadas correctamente. La carga de trabajo de FSharpLint que motiva esta mejora combinaba repetidamente reglas que no detectaban advertencias. Core ahora reutiliza arrays vacíos compartidos en más operaciones:
let checkTitle (title: string) =
if String.IsNullOrWhiteSpace title then [|"A title is required."|]
else Array.empty
let warnings = ["Introduction"; "Installation"] |> List.toArray |> Array.collect checkTitle
let sharedEmpty = obj.ReferenceEquals(warnings, Array.empty<string>)
// true: no new empty result array
Este cambio elimina los objetos de matriz vacía redundantes, pero no todas las asignaciones de memoria de la canalización. No dé por sentado que los distintos resultados vacíos tienen identidades de referencia diferentes.
Gracias a @T-Gro por su contribución a las devoluciones de llamada insertadas de colecciones y las matrices vacías compartidas.
Llamadas a API más precisas y mejoras prácticas para el uso cotidiano
Exigir nombres cuando puede cambiar el orden generado de los parámetros
Para proteger las API generadas frente a cambios silenciosos en el orden de los parámetros, un autor puede requerir argumentos con nombre:
Note
La aplicación de esta restricción es una característica de F# 11.2; en el SDK de .NET 11.0.100, habilite las características en versión preliminar. También es necesario disponer de una definición de System.Diagnostics.CodeAnalysis.RequireNamedArgumentsAttribute. El ejemplo completo incluye una definición de compatibilidad para las referencias que carecen de ella.
open System.Diagnostics.CodeAnalysis
type Query =
[<RequireNamedArguments>]
static member Execute(numerator: decimal, denominator: decimal) =
numerator / denominator
let ratio = Query.Execute(numerator = 3m, denominator = 4m)
// 0.75M; Query.Execute(3m, 4m) is rejected.
Al reordenar la declaración, se preserva el resultado de la llamada con argumentos con nombre, 0.75M; se rechaza una llamada con argumentos posicionales. Para el caso de uso de la API generada, el proveedor debe emitir la anotación.
Reconocer un argumento genérico ya encapsulado
Para evitar encapsular una tarea existente como un valor de resultado simple, una nueva regla de resolución de sobrecargas que se aplica como último recurso da preferencia a Task<'T> sobre 'T sin encapsular:
Note
El comportamiento de desempate más concreto y prioridad de sobrecarga son las características F# 11.2; en el SDK de .NET 11.0.100, habilite la versión preliminar.
open System.Threading.Tasks
let pending = Task.FromResult 42
let wrapped = ValueTask<_>(pending)
let answer: int = wrapped.Result
// ValueTask<int>, not ValueTask<Task<int>>
El resultado es ValueTask<int>, no ValueTask<Task<int>>. Consulte Selección de sobrecargas para conocer las limitaciones de esta regla.
Preferir una nueva sobrecarga sin quitar el punto de entrada anterior
Es posible que una biblioteca mantenga un método de diagnóstico antiguo para los archivos binarios existentes al agregar una sobrecarga que incluya la ubicación en el código fuente de quien llama.
OverloadResolutionPriorityAttribute hace que esa preferencia sea explícita para el código que llama recién compilado:
open System.Runtime.CompilerServices
type Check =
static member That(condition: bool) =
if condition then "OK" else "Check failed."
[<OverloadResolutionPriority(1)>]
static member That(condition: bool, [<CallerLineNumber>] ?line: int) =
if condition then "OK" else $"Check failed at line {defaultArg line 0}."
let stock = 0
let diagnostic = Check.That(stock > 0)
// The diagnostic includes the caller's source line.
La llamada sin modificar selecciona la sobrecarga que tiene en cuenta la información de línea. Consulte Selección de sobrecargas para ver el ámbito y las reglas de compatibilidad.
Utilizar las garantías de nulabilidad condicional de .NET
Si la ruta de acceso del archivo no es nula, el resultado que admite valores null de una API de Path no debería obligarle a realizar una comprobación que el propio contrato de la API descarta. F# 11 reconoce la garantía existente de .NET NotNullIfNotNull: una entrada con nombre que no sea nula implica un resultado no null.
open System.IO
let fileName: string = Path.GetFileName("reports/annual.csv")
let extension: string = Path.GetExtension(fileName)
printn (extension.ToUpperInvariant())
// .CSV
let fileNameOf (path: string | null) : string | null =
Path.GetFileName path
Habilite <Nullable>enable</Nullable>. Las entradas que admiten valores NULL siguen requiriendo un tratamiento de estos valores; consulte Contratos de devolución condicional. Para las API de C# con T? sin restricciones, utilice el método auxiliar independiente Unchecked.withNull.
Gracias a @kerams por la compatibilidad con la nulabilidad condicional.
Pequeñas mejoras lingüísticas
Para invocar un tipo de registro con sintaxis de constructor .NET, use los constructores de registros de F# 11.2:
type Person = { Name: string; Age: int }
let person = Person(Name = "Ada", Age = 36)
Los argumentos con nombre evitan depender del orden de los campos. Gracias a @charlesroddie por esta característica.
Para el origen condicional, #elif evita los grupos anidados #if :
#if EU_WAREHOUSE
let dispatchTimeZone = "Europe/Paris"
#elif US_WAREHOUSE
let dispatchTimeZone = "America/New_York"
#else
let dispatchTimeZone = "UTC"
#endif
System.Console.WriteLine(dispatchTimeZone)
La primera rama coincidente gana. El proyecto de ejemplo define EU_WAREHOUSE e imprime Europe/Paris; sin ningún símbolo definido, la salida es UTC.
Para los casos relacionados de herramientas e interoperabilidad:
- Para identificar su cadena de herramientas interactiva, utilice
#version. - Para compartir el código fuente con un host externo, F# acepta líneas
#:de directiva de host sin interpretarlas como instrucciones de paquete o compilación. - Para evitar declaraciones de interfaz redundantes, use la implementación predeterminada de una interfaz derivada.
- Para leer el estado heredado dentro de un cierre normal, usa el nuevo soporte para campos protegidos de la clase base.
Antes de actualizar
Tenga en cuenta estos cambios derivados al actualizar:
| Código afectado | Action |
|---|---|
Bibliotecas con archivos .fsi |
Copie los atributos de contrato de aplicación obligatoria en las firmas; las omisiones ahora generan errores FS3888. |
| Bibliotecas de funciones insertadas basadas en extensiones | Revise las firmas inferidas y los requisitos de productor/consumidor. La inferencia de tipos de F# 11.2 puede conservar restricciones más generales. |
| Delegados de métodos de instancia | Valide las instancias receptoras antes de la construcción directa, ya que los errores provocados por instancias receptoras nulas se producen ahora. |
| Salida interpolada localizada | Los marcadores de posición de formato .NET utilizan la referencia cultural invariable. Seleccione una referencia cultural explícita cuando desee obtener una salida localizada. |
Generadores personalizados con do! final |
Proporcione enlace y finalización de tipo unit; do! al final de una expresión ya no selecciona los miembros de reenvío final. |
Sin terminar let! |
Agregue el cuerpo de cálculo siguiente; ahora, un enlace incompleto genera un diagnóstico más directo. |
| Configuración del lenguaje anterior | Los modos numerados admitidos comienzan en F# 8; Se quita el modo de sangría no estricta. Consulte Versión del lenguaje y sangría. |
| Operaciones bit a bit con enumeraciones | Use enumeraciones con un tipo entero subyacente; se rechazan los tipos subyacentes no válidos. |