Novedades de F# 6
F# 6 agrega varias mejoras al lenguaje F# y a F# interactivo. Se ha publicado con .NET 6.
Puede descargar el SDK de .NET más reciente de la página de descargas de .NET.
Introducción
F# 6 está disponible en todas las distribuciones y herramientas Visual Studio .NET Core. Para obtener más información, vea Introducción a F#.
tarea {...}
F# 6 incluye compatibilidad nativa para crear tareas de .NET en código de F#. Por ejemplo, considere el siguiente código de F# para crear un . Tarea compatible con NET:
let readFilesTask (path1, path2) =
async {
let! bytes1 = File.ReadAllBytesAsync(path1) |> Async.AwaitTask
let! bytes2 = File.ReadAllBytesAsync(path2) |> Async.AwaitTask
return Array.append bytes1 bytes2
} |> Async.StartAsTask
Con F# 6, este código se puede reescribir como se muestra a continuación.
let readFilesTask (path1, path2) =
task {
let! bytes1 = File.ReadAllBytesAsync(path1)
let! bytes2 = File.ReadAllBytesAsync(path2)
return Array.append bytes1 bytes2
}
La compatibilidad con tareas estaba disponible para F# 5 a través de las excelentes bibliotecas TaskBuilder.fs y Ply. Debería ser sencillo migrar código a la compatibilidad integrada. Sin embargo, hay algunas diferencias: los espacios de nombres y la inferencia de tipos difieren ligeramente entre la compatibilidad integrada y estas bibliotecas, y es posible que se necesiten algunas anotaciones de tipo adicionales. Si es necesario, puede seguir usando estas bibliotecas de la comunidad con F# 6 si hace referencia a ellas explícitamente y abre los espacios de nombres correctos en cada archivo.
El task {…} uso de es muy similar al uso de async {…} . El task {…} uso de tiene varias ventajas con respecto a async {…} :
- El rendimiento de
task {…}es mucho mejor. - La depuración paso a paso y seguimientos de pila
task {…}para es mejor. - Es más fácil interoperar con paquetes de .NET que esperan o generan tareas.
Si está familiarizado con async {…} , hay algunas diferencias que debe tener en cuenta:
task {…}ejecuta inmediatamente la tarea hasta el primer punto await.task {…}no propaga implícitamente un token de cancelación.task {…}no realiza comprobaciones de cancelación implícitas.task {…}no admite llamadas de cola asincrónicas. Esto significa que el uso recursivo puede dar lugar a desbordamientos de pila sireturn! ..no hay esperas asincrónicas que intervenan.
En general, debe considerar la posibilidad de usar over en código nuevo si va a interoperar con bibliotecas de .NET que usan tareas y si no se basa en llamadas finales de código asincrónicas o propagación implícita de tokens de task {…} async {…} cancelación. En el código existente, solo debe cambiar a una vez que haya revisado el código para asegurarse de que no se basa en las características task {…} mencionadas anteriormente de async {…} .
Esta característica implementa F# RFC FS-1097.
Sintaxis de indexación más sencilla con expr[idx]
F# 6 permite la expr[idx] sintaxis para indexar y cortar colecciones.
Hasta F# 5 y hasta su inclusión, F# se ha usado como expr.[idx] sintaxis de indexación. Permitir el uso de se basa en comentarios repetidos de aquellos que aprenden F# o ven F# por primera vez que el uso de la indexación de notación de puntos se produce como una discrepancia innecesaria con respecto a la práctica estándar del expr[idx] sector.
No se trata de un cambio importante porque, de forma predeterminada, no se emite ninguna advertencia sobre el uso de expr.[idx] . Sin embargo, se emiten algunos mensajes informativos que sugieren aclaraciones de código. Opcionalmente, también puede activar más mensajes informativos. Por ejemplo, puede activar una advertencia informativo opcional ( ) para iniciar la generación /warnon:3566 de informes de los usos de la expr.[idx] notación. Para obtener más información, vea Indexer Notation.
En el nuevo código, se recomienda el uso sistemático de como expr[idx] sintaxis de indexación.
Esta característica implementa F# RFC FS-1110.
Representaciones de estructura para patrones activos parciales
F# 6 aumenta la característica "patrones activos" con representaciones de struct opcionales para patrones activos parciales. Esto le permite usar un atributo para restringir un patrón activo parcial para devolver una opción de valor:
[<return: Struct>]
let (|Int|_|) str =
match System.Int32.TryParse(str) with
| true, int -> ValueSome(int)
| _ -> ValueNone
Se requiere el uso del atributo . En los sitios de uso, el código no cambia. El resultado neto es que se reducen las asignaciones.
Esta característica implementa F# RFC FS-1039.
Operaciones personalizadas sobrecargadas en expresiones de cálculo
F# 6 permite consumir interfaces con implementaciones predeterminadas.
Considere el siguiente uso de un generador de expresiones de content cálculo:
let mem = new System.IO.MemoryStream("Stream"B)
let content = ContentBuilder()
let ceResult =
content {
body "Name"
body (ArraySegment<_>("Email"B, 0, 5))
body "Password"B 2 4
body "BYTES"B
body mem
body "Description" "of" "content"
}
Aquí, body la operación personalizada toma un número variable de argumentos de distintos tipos. Esto es compatible con la implementación del siguiente generador, que usa la sobrecarga:
type Content = ArraySegment<byte> list
type ContentBuilder() =
member _.Run(c: Content) =
let crlf = "\r\n"B
[|for part in List.rev c do
yield! part.Array[part.Offset..(part.Count+part.Offset-1)]
yield! crlf |]
member _.Yield(_) = []
[<CustomOperation("body")>]
member _.Body(c: Content, segment: ArraySegment<byte>) =
segment::c
[<CustomOperation("body")>]
member _.Body(c: Content, bytes: byte[]) =
ArraySegment<byte>(bytes, 0, bytes.Length)::c
[<CustomOperation("body")>]
member _.Body(c: Content, bytes: byte[], offset, count) =
ArraySegment<byte>(bytes, offset, count)::c
[<CustomOperation("body")>]
member _.Body(c: Content, content: System.IO.Stream) =
let mem = new System.IO.MemoryStream()
content.CopyTo(mem)
let bytes = mem.ToArray()
ArraySegment<byte>(bytes, 0, bytes.Length)::c
[<CustomOperation("body")>]
member _.Body(c: Content, [<ParamArray>] contents: string[]) =
List.rev [for c in contents -> let b = Text.Encoding.ASCII.GetBytes c in ArraySegment<_>(b,0,b.Length)] @ c
Esta característica implementa F# RFC FS-1056.
Patrones "as"
En F# 6, el lado derecho de un as patrón ahora puede ser un patrón. Esto es importante cuando una prueba de tipo ha dado un tipo más fuerte a una entrada. Por ejemplo, considere el siguiente código:
type Pair = Pair of int * int
let analyzeObject (input: obj) =
match input with
| :? (int * int) as (x, y) -> printfn $"A tuple: {x}, {y}"
| :? Pair as Pair (x, y) -> printfn $"A DU: {x}, {y}"
| _ -> printfn "Nope"
let input = box (1, 2)
En cada caso de patrón, se prueba el tipo del objeto de entrada. Ahora se permite que el lado derecho del patrón sea un patrón más, que a su vez puede coincidir con el objeto as en el tipo más seguro.
Esta característica implementa F# RFC FS-1105.
Revisiones de sintaxis de sangría
F# 6 quita una serie de incoherencias y limitaciones en el uso de la sintaxis que tiene en cuenta la sangría. Vea RFC FS-1108. Esto resuelve 10 problemas importantes resaltados por los usuarios de F# desde F# 4.0.
Por ejemplo, en F# 5 se permitió el código siguiente:
let c = (
printfn "aaaa"
printfn "bbbb"
)
Sin embargo, no se permitió el código siguiente (se produjo una advertencia):
let c = [
1
2
]
En F# 6, se permiten ambos. Esto hace que F# sea más sencillo y fácil de aprender. Hadrian Tang, colaborador de la comunidad de F#, ha sido el responsable de esto, incluidas pruebas sistemáticas notables y muy valiosas de la característica.
Esta característica implementa F# RFC FS-1108.
Conversiones implícitas adicionales
En F# 6, hemos activado la compatibilidad con conversiones adicionales "implícitas" y "dirigidas a tipos", como se describe en RFC FS-1093.
Este cambio aporta tres ventajas:
- Se requieren menoscasts explícitos.
- Se requieren menos conversiones explícitas de enteros
- Compatibilidad de primera clase con . Se agregan conversiones implícitas de estilo NET
Esta característica implementa F# RFC FS-1093.
Conversiones de conversión upcast implícitas adicionales
F# 6 implementa conversiones de conversión upcast implícitas adicionales. Por ejemplo, en F# 5 y versiones anteriores, se necesitaban actualizaciones para la expresión de devolución al implementar una función en la que las expresiones tenían subtipos diferentes en distintas ramas, incluso cuando había una anotación de tipo. Considere el siguiente código de F# 5:
open System
open System.IO
let findInputSource () : TextReader =
if DateTime.Now.DayOfWeek = DayOfWeek.Monday then
// On Monday a TextReader
Console.In
else
// On other days a StreamReader
File.OpenText("path.txt") :> TextReader
En este caso, las ramas del proceso condicional son y , respectivamente, y se ha agregado la difusión para que ambas ramas tengan TextReader StreamReader el tipo StreamReader. En F# 6, estos upcasts ahora se agregan automáticamente. Esto significa que el código es más sencillo:
let findInputSource () : TextReader =
if DateTime.Now.DayOfWeek = DayOfWeek.Monday then
// On Monday a TextReader
Console.In
else
// On other days a StreamReader
File.OpenText("path.txt")
Opcionalmente, puede habilitar la advertencia para mostrar una advertencia en cada momento en que se usa una conversión upcast implícita adicional, como se describe en Advertencias opcionales para /warnon:3388 conversiones implícitas.
Conversiones implícitas de enteros
En F# 6, los enteros de 32 bits se amplían a enteros de 64 bits cuando se conocen ambos tipos. Por ejemplo, considere una forma de API típica:
type Tensor(…) =
static member Create(sizes: seq<int64>) = Tensor(…)
En F# 5, se deben usar literales enteros para int64:
Tensor.Create([100L; 10L; 10L])
o
Tensor.Create([int64 100; int64 10; int64 10])
En F# 6, el ensanchamiento se produce automáticamente para a , a y a , cuando se conocen tanto el tipo de origen como el de destino durante la int32 int64 int32 nativeint int32 double inferencia de tipos. Por lo tanto, en casos como los ejemplos anteriores, int32 se pueden usar literales:
Tensor.Create([100; 10; 10])
A pesar de este cambio, F# sigue usando el ampliación explícito de tipos numéricos en la mayoría de los casos. Por ejemplo, el ampliación implícito no se aplica a otros tipos numéricos, como o , o de a , o cuando se desconoce el tipo de origen int8 int16 o float32 float64 destino. Opcionalmente, también puede habilitar la advertencia para mostrar una advertencia en cada momento en que se usa el ampliación numérico implícito, como se describe en Advertencias opcionales para /warnon:3389 conversiones implícitas.
Compatibilidad de primera clase con . Conversiones implícitas de estilo NET
En F# 6, las conversiones "op_Implicit" de .NET se aplican automáticamente en el código de F# al llamar a métodos. Por ejemplo, en F# 5 era necesario usar al trabajar con api XName.op_Implicit de .NET para XML:
open System.Xml.Linq
let purchaseOrder = XElement.Load("PurchaseOrder.xml")
let partNos = purchaseOrder.Descendants(XName.op_Implicit "Item")
En F# 6, las conversiones se aplican automáticamente a las expresiones de argumento cuando los tipos están disponibles para la expresión de origen op_Implicit y el tipo de destino:
open System.Xml.Linq
let purchaseOrder = XElement.Load("PurchaseOrder.xml")
let partNos = purchaseOrder.Descendants("Item")
Opcionalmente, puede habilitar la advertencia para mostrar una advertencia en cada momento en que se usa el ampliación numérico implícito, como se describe en Advertencias opcionales para /warnon:3395 conversiones implícitas.
Nota
En la primera versión de F# 6, este número de advertencia era /warnon:3390 . Debido a un conflicto, el número de advertencia se actualizó posteriormente a /warnon:3395 .
Advertencias opcionales para conversiones implícitas
Las conversiones implícitas y dirigidas a tipos pueden interactuar mal con la inferencia de tipos y dar lugar a código que es más difícil de entender. Por este motivo, existen algunas mitigaciones para ayudar a garantizar que esta característica no se abuse en el código de F#. En primer lugar, el tipo de origen y el de destino deben ser muy conocidos, sin que surja ambigüedad ni inferencia de tipos adicional. En segundo lugar, se pueden activar advertencias de opt-in para notificar cualquier uso de conversiones implícitas, con una advertencia activada de forma predeterminada:
/warnon:3388(conversión upcast implícita adicional)/warnon:3389(ampliación numérica implícita)/warnon:3391(op_Implicit en argumentos que no son de método, de forma predeterminada)/warnon:3395(op_Implicit en argumentos de método)
Si su equipo desea prohibir todos los usos de conversiones implícitas, también puede especificar /warnaserror:3388 , /warnaserror:3389 , y /warnaserror:3391 /warnaserror:3395 .
Formato de números binarios
F# 6 agrega el %B patrón a los especificadores de formato disponibles para formatos de números binarios. Tenga en cuenta el siguiente código de F#:
printf "%o" 123
printf "%B" 123
Este código imprime la salida siguiente:
173
1111011
Esta característica implementa F# RFC FS-1100.
Descartes en enlaces de uso
F# 6 permite _ usarse en un use enlace, por ejemplo:
let doSomething () =
use _ = System.IO.File.OpenText("input.txt")
printfn "reading the file"
Esta característica implementa F# RFC FS-1102.
InlineIfLambda
El compilador de F# incluye un optimizador que realiza la inlineación de código. En F# 6 hemos agregado una nueva característica declarativa que permite que el código indique opcionalmente que, si se determina que un argumento es una función lambda, ese argumento siempre debe incluirse en los sitios de llamada.
Por ejemplo, considere la siguiente iterateTwice función para recorrer una matriz:
let inline iterateTwice ([<InlineIfLambda>] action) (array: 'T[]) =
for j = 0 to array.Length-1 do
action array[j]
for j = 0 to array.Length-1 do
action array[j]
Si el sitio de llamada es:
let arr = [| 1.. 100 |]
let mutable sum = 0
arr |> iterateTwice (fun x ->
sum <- sum + x)
Después de la inlineación y otras optimizaciones, el código se convierte en:
let arr = [| 1.. 100 |]
let mutable sum = 0
for j = 0 to array.Length-1 do
sum <- array[i] + x
for j = 0 to array.Length-1 do
sum <- array[i] + x
A diferencia de las versiones anteriores de F#, esta optimización se aplica independientemente del tamaño de la expresión lambda implicada. Esta característica también se puede usar para implementar la desrollación de bucles y transformaciones similares de forma más confiable.
Se puede desactivar una advertencia opcional (, desactivada de forma predeterminada) para indicar lugares en el código en los que los argumentos no están enlazados a expresiones lambda en sitios /warnon:3517 InlineIfLambda de llamada. En situaciones normales, esta advertencia no debe habilitarse. Sin embargo, en ciertos tipos de programación de alto rendimiento, puede ser útil asegurarse de que todo el código se inlinee y aplane.
Esta característica implementa F# RFC FS-1098.
Código reanudable
La compatibilidad de F# 6 se basa en una base task {…} denominada código reanudable RFC FS-1087. El código reanudable es una característica técnica que se puede usar para crear muchos tipos de máquinas de estado asincrónicas de alto rendimiento y de rendimiento.
Funciones de colección adicionales
FSharp.Core 6.0.0 agrega cinco nuevas operaciones a las funciones de colección principales. Estas funciones son las siguientes:
- List/Array/Seq.insertAt
- List/Array/Seq.removeAt
- List/Array/Seq.updateAt
- List/Array/Seq.insertManyAt
- List/Array/Seq.removeManyAt
Todas estas funciones realizan operaciones de copia y actualización en el tipo de colección o secuencia correspondiente. Este tipo de operación es una forma de una "actualización funcional". Para obtener ejemplos del uso de estas funciones, consulte la documentación correspondiente, por ejemplo, List.insertAt.
Por ejemplo, considere el modelo, el mensaje y la lógica de actualización para una sencilla aplicación "Lista de tareas pendientes" escrita en el estilo El reintento. Aquí, el usuario interactúa con la aplicación, genera mensajes y la función procesa estos update mensajes, generando un nuevo modelo:
type Model =
{ ToDo: string list }
type Message =
| InsertToDo of index: int * what: string
| RemoveToDo of index: int
| LoadedToDos of index: int * what: string list
let update (model: Model) (message: Message) =
match message with
| InsertToDo (index, what) ->
{ model with ToDo = model.ToDo |> List.insertAt index what }
| RemoveToDo index ->
{ model with ToDo = model.ToDo |> List.removeAt index }
| LoadedToDos (index, what) ->
{ model with ToDo = model.ToDo |> List.insertManyAt index what }
Con estas nuevas funciones, la lógica es clara y sencilla y solo se basa en datos inmutables.
Esta característica implementa F# RFC FS-1113.
El mapa tiene claves y valores
En FSharp.Core 6.0.0, el tipo ahora admite Map las propiedades Claves y Valores. Estas propiedades no copian la colección subyacente.
Esta característica se documenta en F# RFC FS-1113.
Intrínsecos adicionales para NativePtr
FSharp.Core 6.0.0 agrega nuevos intrínsecos al módulo NativePtr:
NativePtr.nullPtrNativePtr.isNullPtrNativePtr.initBlockNativePtr.clearNativePtr.copyNativePtr.copyBlockNativePtr.ofILSigPtrNativePtr.toILSigPtr
Al igual que con otras funciones NativePtr de , estas funciones se inlinean y su uso emite advertencias a menos que se use /nowarn:9 . El uso de estas funciones está restringido a los tipos no administrados.
Esta característica se documenta en F# RFC FS-1109.
Tipos numéricos adicionales con anotaciones unitarias
En F# 6, los siguientes tipos o alias de abreviatura de tipo ahora admiten anotaciones de unidad de medida. Las nuevas adiciones se muestran en negrita:
| Alias de F# | Tipo CLR |
|---|---|
float32/single |
System.Single |
float/double |
System.Double |
decimal |
System.Decimal |
sbyte/int8 |
System.SByte |
int16 |
System.Int16 |
int/int32 |
System.Int32 |
int64 |
System.Int64 |
byte/uint8 |
System.Byte |
uint16 |
System.UInt16 |
uint/uint32 |
System.UInt32 |
uint64 |
System.UIn64 |
nativeint |
System.IntPtr |
unativeint |
System.UIntPtr |
Por ejemplo, puede anotar un entero sin signo como se indica a continuación:
[<Measure>]
type days
let better_age = 3u<days>
Esta característica se documenta en F# RFC FS-1091.
Advertencias informativos para operadores simbólicos que rara vez se usan
F# 6 agrega instrucciones flexibles que des normalizan el uso de := , , y en ! incr decr F# 6 y posteriores. El uso de estos operadores y funciones genera mensajes informativos que le piden que reemplace el código por un uso explícito de la Value propiedad .
En la programación de F#, se pueden usar celdas de referencia para registros mutables asignados por montón. Aunque en ocasiones son útiles, rara vez se necesitan en la codificación moderna de F#, ya que let mutable se pueden usar en su lugar. La biblioteca principal de F# incluye dos operadores := y dos funciones y relacionadas ! incr decr específicamente con las llamadas de referencia. La presencia de estos operadores hace que las celdas de referencia sean más centrales para la programación de F# de lo que deben ser, lo que requiere que todos los programadores de F# conozcan estos operadores. Además, el operador se puede confundir fácilmente con la operación en C# y otros lenguajes, una fuente potencialmente sutil de errores al ! not traducir código.
La lógica de este cambio es reducir el número de operadores que el programador de F# necesita conocer y, por tanto, simplificar F# para principiantes.
Por ejemplo, considere el siguiente código de F# 5:
let r = ref 0
let doSomething() =
printfn "doing something”
r := !r + 1
En primer lugar, las celdas de referencia rara vez son necesarias en la codificación F# moderna, como normalmente se let mutable puede usar en su lugar:
let mutable r = 0
let doSomething() =
printfn "doing something”
r <- r + 1
Si usa celdas de referencia, F# 6 emite una advertencia informativo en la que se le pide que cambie la última línea a y que le vincule a otras instrucciones sobre el uso adecuado de las celdas de r.Value <- r.Value + 1 referencia.
let r = ref 0
let doSomething() =
printfn "doing something”
r.Value <- r.Value + 1
Estos mensajes no son advertencias; son "mensajes informativos" que se muestran en el IDE y la salida del compilador. F# sigue siendo compatible con versiones anteriores.
Esta característica implementa F# RFC FS-1111.
Herramientas de F#: .NET 6 es el valor predeterminado para el scripting en Visual Studio
Si abre o ejecuta un script de F# ( ) en Visual Studio, el script se analizará y ejecutará de forma predeterminada mediante .NET 6 con una ejecución de .fsx 64 bits. Esta funcionalidad ha estado en versión preliminar en las versiones posteriores de Visual Studio 2019 y ahora está habilitada de forma predeterminada.
Para habilitar el .NET Framework, seleccione Opciones > de herramientas Herramientas Herramientas de > F# > F# interactivo. Establezca Use .NET Core Scripting (Usar scripting de .NET Core) en false y, a continuación, reinicie F# interactivo ventana. Esta configuración afecta tanto a la edición de scripts como a la ejecución de scripts. Para habilitar la ejecución de 32 bits para .NET Framework scripting, establezca también 64 bits F# interactivo en false. No hay ninguna opción de 32 bits para el scripting de .NET Core.
Herramientas de F#: Anclar la versión del SDK de los scripts de F#
Si ejecuta un script mediante en un directorio que contiene un archivo global.json con una configuración del SDK de .NET, la versión enumerada del SDK de .NET se usará para ejecutar y editar dotnet fsi el script. Esta característica ha estado disponible en las versiones posteriores de F# 5.
Por ejemplo, suponga que hay un script en un directorio con el siguiente archivo global.json que especifica una directiva de versión del SDK de .NET:
{
"sdk": {
"version": "5.0.200",
"rollForward": "minor"
}
}
Si ahora ejecuta el script mediante , desde este directorio, se respetará la versión dotnet fsi del SDK. Se trata de una característica eficaz que le permite "bloquear" el SDK que se usa para compilar, analizar y ejecutar los scripts.
Si abre y edita el script en Visual Studio y otros IDE, las herramientas respetarán esta configuración al analizar y comprobar el script. Si no se encuentra el SDK, deberá instalarlo en la máquina de desarrollo.
En Linux y otros sistemas Unix, puede combinarlo con una versión de lenguaje para especificar también una versión del lenguaje para la ejecución directa del script. Una sencilla tarea para script.fsx es:
#!/usr/bin/env -S dotnet fsi
printfn "Hello, world"
Ahora el script se puede ejecutar directamente con script.fsx . Puede combinar esto con una versión de lenguaje específica no predeterminada como esta:
#!/usr/bin/env -S dotnet fsi --langversion:5.0
Nota
Esta configuración se omite mediante las herramientas de edición, que analizarán el script suponiendo la versión más reciente del lenguaje.
Eliminación de características heredadas
Desde F# 2.0, algunas características heredadas en desuso han dado advertencias durante mucho tiempo. El uso de estas características en F# 6 proporciona errores a menos que use explícitamente /langversion:5.0 . Las características que dan errores son:
- Varios parámetros genéricos que usan un nombre de tipo postfijo, por ejemplo
(int, int) Dictionary. Esto se convierte en un error en F# 6. En suDictionary<int,int>lugar, se debe usar la sintaxis estándar. #indent "off". Esto se convierte en un error.x.(expr). Esto se convierte en un error.module M = struct … end. Esto se convierte en un error.- Uso de entradas
*.mly*.mli. Esto se convierte en un error. - Uso de
(*IF-CAML*)o(*IF-OCAML*). Esto se convierte en un error. - Uso de
land, , , , o como operadores delorlxorlsllsrasrinfijo. Se trata de palabras clave infix en F# porque eran palabras clave infix en OCaml y no se definen en FSharp.Core. Ahora, el uso de estas palabras clave emitirá una advertencia.
Esto implementa F# RFC FS-1114.