Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
F# 6 agrega varias mejoras al lenguaje F# y a F# Interactive. Se publica con .NET 6.
Puede descargar el SDK de .NET más reciente desde la página de descargas de .NET.
Comienza
F# 6 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#.
tarea {...}
F# 6 incluye compatibilidad nativa con la creación de tareas de .NET en código de F# mediante expresiones de tarea. Las expresiones de tarea son similares a las expresiones asincrónicas, pero permiten crear tareas de .NET directamente.
Por ejemplo, considere el siguiente código de F# para crear una 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 volver a escribir de la siguiente manera.
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. Debe 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 utilizando estas bibliotecas comunitarias con F# 6 si hace referencia a ellas explícitamente y abre los espacios de nombres apropiados en cada archivo.
El uso task {…} de es muy similar al uso de async {…}. El uso task {…} de tiene varias ventajas sobre async {…}:
- La sobrecarga de
task {...}es menor, lo que posiblemente mejora el rendimiento en las secciones del código frecuentemente ejecutadas, donde el trabajo asincrónico se realiza rápidamente. - La depuración del seguimiento paso a paso y los rastros de pila para
task {…}es mejor. - La interoperación con paquetes .NET que esperan o generan tareas es más fácil.
Si está familiarizado con async {…}, hay algunas diferencias que debe tener en cuenta:
-
task {…}ejecuta inmediatamente la tarea hasta el primer punto de espera. -
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 usoreturn! ..de forma recursiva puede dar lugar a desbordamientos de pila si no hay esperas asincrónicas intermedias.
En general, debe considerar usar task {…} en lugar de async {…} en nuevo código si interopera con bibliotecas de .NET que usan tasks y no depende de llamadas de cola asincrónicas de código o propagación implícita de tokens de cancelación. En el código existente, solo debe cambiar a task {…} una vez que haya revisado su código para asegurarse de no basarse en las características 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 sintaxis expr[idx] para indexar y segmentar colecciones.
Hasta e incluyendo F# 5, F# ha utilizado expr.[idx] como sintaxis de indexación. Permitir el uso de expr[idx] se basa en comentarios repetidos de aquellos que aprenden F# o descubren F# por primera vez, quienes consideran que la indexación por punto es vista como una innecesaria divergencia de la práctica estándar de la industria.
Este no es un cambio importante porque, de forma predeterminada, no se emiten advertencias en el uso de expr.[idx]. Sin embargo, se emiten algunos mensajes informativos que sugieren aclaraciones de código. También puede activar más mensajes informativos. Por ejemplo, puede activar una advertencia informativa opcional (/warnon:3366) para empezar a informar sobre el uso de la notación expr.[idx]. Para obtener más información, vea Notación del indexador.
En el nuevo código, recomendamos el uso sistemático de expr[idx] como la 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 estructura 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 usar CustomOperationAttribute en los métodos sobrecargados.
Tenga en cuenta el uso siguiente de un generador contentde expresiones de 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í la body operación personalizada toma un número variable de argumentos de distintos tipos. Esto es compatible con la implementación del constructor siguiente, que usa 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 tipo "como"
En F# 6, el lado derecho de un patrón as ahora puede ser un patrón. Esto es importante cuando una prueba de tipo ha proporcionado un tipo más seguro a una entrada. Por ejemplo, considere el código siguiente:
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, el objeto de entrada se prueba por tipos. Ahora se permite que el lado derecho del as patrón sea un patrón adicional, que puede coincidir con el objeto en el tipo más fuerte.
Esta característica implementa F# RFC FS-1105.
Revisiones de la sintaxis de sangría
F# 6 quita una serie de incoherencias y limitaciones en su uso de la sintaxis sensible a la sangría. Consulte RFC FS-1108. Esto resuelve 10 problemas significativos 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 (generó una advertencia):
let c = [
1
2
]
En F# 6, ambos están permitidos. Esto hace que F# sea más sencillo y más fácil de aprender. El colaborador de la comunidad de F# Hadrian Tang ha llevado el camino hacia esto, incluyendo 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 "implícitas" y "dirigidas por tipo", según se describe en RFC FS-1093.
Este cambio aporta tres ventajas:
- Se requieren menos conversiones de tipo explícitas
- Se requieren menos conversiones de entero explícitas
- Se ha añadido compatibilidad de primera clase para conversiones implícitas de estilo .NET.
Esta característica implementa F# RFC FS-1093.
Conversiones de difusión implícita adicionales
F# 6 implementa conversiones ascendentes implícitas adicionales. Por ejemplo, en F# 5 y versiones anteriores, se necesitaban actualizaciones para la expresión de retorno al implementar una función en la que las expresiones tenían subtipos diferentes en ramas diferentes, incluso cuando había una anotación de tipo presente. Tenga en cuenta 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
Aquí las ramas del condicional calculan a TextReader y StreamReader respectivamente, y el ascenso de tipo se agregó para que ambas ramas tengan el tipo StreamReader. En F# 6, estas difusión 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 /warnon:3388 para mostrar un aviso cada vez que se utiliza una conversión implícita adicional hacia arriba, como se describe en Advertencias opcionales para conversiones implícitas.
Conversiones de enteros implícitas
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 típica de API:
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, la ampliación se produce automáticamente para int32 a int64, int32 a nativeint, y int32 a double, cuando se conocen tanto el tipo de origen como el de destino durante la inferencia de tipos. Por lo tanto, en casos como los ejemplos anteriores, los literales int32 se pueden usar.
Tensor.Create([100; 10; 10])
A pesar de este cambio, F# sigue usando la ampliación explícita de tipos numéricos en la mayoría de los casos. Por ejemplo, el ampliación implícita no se aplica a otros tipos numéricos, como int8 o int16, o de float32 a float64, o cuando se desconoce el tipo de origen o de destino. También puede habilitar opcionalmente la advertencia /warnon:3389 para mostrar una advertencia en cada punto se usa la ampliación numérica implícita, como se describe en Advertencias opcionales para conversiones implícitas.
Compatibilidad de primera clase con conversiones implícitas al estilo .NET
En F# 6, las conversiones de "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 XName.op_Implicit al trabajar con las API 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, op_Implicit las conversiones se aplican automáticamente para las expresiones de argumento cuando los tipos están disponibles para la expresión de origen 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 /warnon:3395 para mostrar una advertencia en cada punto donde se usa una conversión de ensanchamiento en los argumentos de un método, como se describe en Advertencias opcionales para 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ó más tarde 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 provocar que el código sea más difícil de entender. Por este motivo, existen algunas mitigaciones para ayudar a garantizar que esta característica no se ha abusado en el código de F#. En primer lugar, tanto el tipo de origen como el de destino deben ser muy conocidos, sin ambigüedad ni inferencia de tipos adicionales que surjan. En segundo lugar, las advertencias de participación se pueden activar para notificar cualquier uso de conversiones implícitas, con una advertencia de forma predeterminada:
-
/warnon:3388(difusión implícita adicional) -
/warnon:3389(ampliación numérica implícita) -
/warnon:3391(op_Implicit en argumentos que no son métodos, activados de forma predeterminada) -
/warnon:3395(op_Implicit en argumentos de método)
Si el equipo quiere prohibir todos los usos de conversiones implícitas, también puede especificar /warnaserror:3388, /warnaserror:3389, /warnaserror:3391y /warnaserror:3395.
Formato para números binarios
F# 6 agrega el %B patrón a los especificadores de formato disponibles para los formatos de número binario. 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 vinculaciones de uso
F# 6 permite que _ se use en una asignación de use, 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 inserción de código. En F# 6 hemos agregado una nueva característica declarativa que permite al código indicar opcionalmente que, si se determina que un argumento es una función lambda, ese argumento siempre debe insertarse 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 inserción en línea y otras optimizaciones, el código se transforma en:
let arr = [| 1.. 100 |]
let mutable sum = 0
for j = 0 to arr.Length-1 do
sum <- sum + arr[j]
for j = 0 to arr.Length-1 do
sum <- sum + arr[j]
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 el desenrollamiento de bucles y transformaciones similares de forma más fiable.
Se puede activar una advertencia opcional (/warnon:3517, desactivada de forma predeterminada) para indicar los lugares en el código donde los argumentos InlineIfLambda no están enlazados a expresiones lambda en los puntos de llamada. En situaciones normales, esta advertencia no debe estar habilitada. Sin embargo, en ciertos tipos de programación de alto rendimiento, puede ser útil asegurarse de que todo el código se integre y simplifique.
Esta característica implementa F# RFC FS-1098.
Código reanudable
La task {…} compatibilidad de F# 6 se basa en una base denominada código reanudableRFC 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 y de alto rendimiento.
Funciones de colección adicionales
FSharp.Core 6.0.0 agrega cinco operaciones nuevas a las funciones de colección principal. Estas funciones son:
- List/Array/Seq.insertAt
- List/Array/Seq.removeAt
- List/Array/Seq.updateAt
- List/Array/Seq.insertManyAt
- List/Array/Seq.removeManyAt
Estas funciones realizan operaciones de copia y actualización en el tipo de colección o secuencia correspondientes. Este tipo de operación es una forma de "actualización funcional". Para obtener ejemplos de 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 Elmish. Aquí el usuario interactúa con la aplicación, generando mensajes y la update función procesa estos 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 Map tipo ahora admite 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 de NativePtr, estas funciones están insertadas y su uso emite advertencias a menos que se use /nowarn:9. El uso de estas funciones está restringido a tipos no administrados.
Esta característica se documenta en F# RFC FS-1109.
Tipos numéricos adicionales con anotaciones de unidad
En F# 6, los siguientes tipos o abreviaturas 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 de la siguiente manera:
[<Measure>]
type days
let better_age = 3u<days>
Esta característica se documenta en F# RFC FS-1091.
Advertencias informativas sobre operadores simbólicos usados raramente
F# 6 agrega instrucciones suaves que des normalizan el uso de :=, !, incry decr en F# 6 y versiones posteriores. El uso de estos operadores y funciones genera mensajes informativos que le piden que reemplace el código por el uso explícito de la Value propiedad .
En la programación de F#, las celdas de referencia se pueden usar para los registros mutables asignados al montón. Aunque ocasionalmente 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 incr y decr específicamente relacionadas 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# 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 not operación en C# y otros lenguajes, una fuente potencialmente sutil de errores al traducir código.
La justificación de este cambio es reducir el número de operadores que el programador de F# necesita saber y, por tanto, simplificar F# para principiantes.
Por ejemplo, considere el siguiente código 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 moderna de F#, como let mutable se puede usar normalmente 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 informativa solicitando que cambie la última línea a r.Value <- r.Value + 1 y lo vincula con más orientación sobre el uso adecuado de las celdas de 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 la guionización en Visual Studio
Si abre o ejecuta un script de F# (.fsx) en Visual Studio, el script se analizará y ejecutará de forma predeterminada mediante .NET 6 con ejecución de 64 bits. Esta funcionalidad estaba en versión preliminar en las versiones posteriores de Visual Studio 2019 y ahora está habilitada de forma predeterminada.
Para habilitar el scripting de .NET Framework, seleccione Herramientas>Opciones>F# Tools>F# Interactive. Establezca Use .NET Core Scripting en false y reinicie la ventana interactiva de F#. 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 el scripting de .NET Framework, establezca también F# interactivo de 64 bits en false. No hay ninguna opción de 32 bits para el scripting de .NET Core.
Herramientas de desarrollo F#: Fijar la versión del SDK para tus scripts de F#
Si ejecuta un script mediante dotnet fsi en un directorio que contiene un archivo deglobal.json con una configuración del SDK de .NET, la versión enumerada del SDK de .NET se usará para ejecutar y editar el script. Esta característica está disponible en las versiones posteriores de F# 5.
Por ejemplo, supongamos 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 dotnet fsi, desde este directorio, se respetará la versión del SDK. Se trata de una característica eficaz que 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 un shebang para especificar también una versión de lenguaje para la ejecución directa del script. Un simple shebang para script.fsx es:
#!/usr/bin/env -S dotnet fsi
printfn "Hello, world"
Ahora el script se puede ejecutar directamente con script.fsx. Puede combinarlo con una versión de idioma específica y no predeterminada como esta:
#!/usr/bin/env -S dotnet fsi --langversion:5.0
Nota:
Esta configuración se omite mediante 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 obsoletas han emitido advertencias. El uso de estas características en F# 6 produce errores a menos que use /langversion:5.0explícitamente . Las características que proporcionan errores son:
- Varios parámetros genéricos mediante un nombre de tipo con sufijo, por ejemplo
(int, int) Dictionary. Esto se convierte en un error en F# 6. En su lugar, se debe usar la sintaxisDictionary<int,int>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,lor,lxor,lsl,lsroasrcomo operadores de infijo. Estas son palabras clave infijas en F# porque eran palabras clave infijas en OCaml y no se definen en FSharp.Core. El uso de estas palabras clave ahora emitirá una advertencia.
Esto implementa F# RFC FS-1114.