Anuncio de la vista previa de F # 4.6

Nos complace anunciar que Visual Studio 2019 enviará una nueva versión de F # cuando se lance: ¡F # 4.6!


F # 4.6 es una actualización más pequeña del lenguaje F #, por lo que es un lanzamiento de punto "verdadero". Al igual que con las versiones anteriores de F #, F # 4.6 se desarrolló completamente a través de un proceso abierto de RFC (solicitudes de comentarios). La comunidad F # ha ofrecido comentarios muy detallados en las discusiones para esta versión del lenguaje. Puede ver todos los RFC que corresponden con esta versión aquí:



Esta publicación detallará el conjunto de características y cómo comenzar.


Original en blog

Empezar


Primero, instale:



A continuación, actualice su dependencia de FSharp.Core a FSharp.Core 4.6 (o superior). Si está utilizando Visual Studio, puede hacerlo con la interfaz de usuario de NuGet Package Management. Si no está utilizando Visual Studio, o prefiere editar manualmente los archivos del proyecto, agregue esto al archivo del proyecto:


<ItemGroup> <PackageReference Update="FSharp.Core" Version="4.6.0" /> </ItemGroup> 

Una vez que haya instalado los bits necesarios, puede usar F # 4.6 con Visual Studio , Visual Studio para Mac o Visual Studio Code con Ionide .


Registros anónimos


Además de varias correcciones de errores, el único cambio de idioma en F # 4.6 es la introducción de tipos de registros anónimos .


Uso básico


Desde una perspectiva exclusiva de F #, los registros anónimos son tipos de registros F # que no tienen nombres explícitos y se pueden declarar en un fasion ad-hoc. Aunque es poco probable que cambien fundamentalmente la forma en que escribe el código F #, llenan muchos vacíos más pequeños que los programadores de F # han encontrado con el tiempo, y pueden usarse para una manipulación de datos sucinta que antes no era posible.


Son bastante fáciles de usar. Por ejemplo, aquí cómo puede interactuar con una función que produce un registro anónimo:


 open System let circleStats radius = let d = radius * 2.0 let a = Math.PI * (radius ** 2.0) let c = 2.0 * Math.PI * radius {| Diameter=d; Area=a; Circumference=c |} let r = 2.0 let stats = circleStats r printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f" r stats.Diameter stats.Area stats.Circumference 

Sin embargo, pueden usarse para algo más que contenedores de datos básicos. A continuación, se amplía la muestra anterior para usar una función de impresión más segura de tipos:


 let circleStats radius = let d = radius * 2.0 let a = Math.PI * (radius ** 2.0) let c = 2.0 * Math.PI * radius {| Diameter=d; Area=a; Circumference=c |} let printCircleStats r (stats: {| Area: float; Circumference: float; Diameter: float |}) = printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f" r stats.Diameter stats.Area stats.Circumference let r = 2.0 let stats = circleStats r printCircleStats r stats 

Si intenta llamar a `printCircleStats` con un registro anónimo que tenía los mismos tipos de datos subyacentes pero diferentes etiquetas, no se compilará:


 printCircleStats r {| Diameter=2.0; Area=4.0; MyCircumference=12.566371 |} // Two anonymous record types have mismatched sets of field names '["Area"; "Circumference"; "Diameter"]' and '["Area"; "Diameter"; "MyCircumference"]' 

Así es exactamente cómo funcionan los tipos de registros F #, excepto que todo se ha declarado ad-hoc en lugar de por adelantado. Esto tiene ventajas e inconvenientes según su situación particular, por lo que recomendamos utilizar juiciosamente los registros anónimos en lugar de reemplazar todas sus declaraciones de registros F # iniciales.


Estructurar registros anónimos


Los registros anónimos también pueden ser estructuras utilizando la palabra clave struct :


 open System let circleStats radius = let d = radius * 2.0 let a = Math.PI * (radius ** 2.0) let c = 2.0 * Math.PI * radius // Note that the keyword comes before the '{| |}' brace pair struct {| Area=a; Circumference=c; Diameter=d |} // the 'struct' keyword also comes before the '{| |}' brace pair when declaring the parameter type let printCircleStats r (stats: struct {| Area: float; Circumference: float; Diameter: float |}) = printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f" r stats.Diameter stats.Area stats.Circumference let r = 2.0 let stats = circleStats r printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f" r stats.Diameter stats.Area stats.Circumference 

Puede llamar a una función que toma un registro anónimo de estructura que se puede hacer explícitamente de esta manera:


 let printCircleStats r (stats: struct {| Area: float; Circumference: float; Diameter: float |}) = printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f" r stats.Diameter stats.Area stats.Circumference printCircleStats r struct {| Area=4.0; Circumference=12.6; Diameter=12.6 |} 

O puede usar "inferencia de estructura" para eludir la "estructura" en el sitio de la llamada:


 let printCircleStats r (stats: struct {| Area: float; Circumference: float; Diameter: float |}) = printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f" r stats.Diameter stats.Area stats.Circumference printCircleStats r {| Area=4.0; Circumference=12.6; Diameter=12.6 |} 

Esto tratará la instancia del registro anónimo que creó como si fuera una estructura.


Tenga en cuenta que lo contrario no es cierto:


 let printCircleStats r (stats: {| Area: float; Circumference: float; Diameter: float |}) = printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f" r stats.Diameter stats.Area stats.Circumference // This will fail to compile for hopefully obvious reasons! printCircleStats r struct {| Area=4.0; Circumference=12.6; Diameter=12.6 |} 

Actualmente no es posible definir los tipos de registro anónimos IsByRefLike o IsReadOnly struct. Hay una sugerencia de lenguaje que propone esta mejora, pero debido a rarezas en la sintaxis todavía está en discusión.


Llevando las cosas más lejos


Los registros anónimos se pueden usar en un conjunto más amplio de contextos más avanzados.


Los registros anónimos son serializables


Puede serializar y deserializar registros anónimos:


 open Newtonsoft.Json let phillip = {| name="Phillip"; age=28 |} let str = JsonConvert.SerializeObject(phillip) printfn "%s" str let phillip' = JsonConvert.DeserializeObject<{|name: string; age: int|}>(str) printfn "Name: %s Age: %d" phillip'.name phillip'.age 

Esto genera lo que puede esperar:


 {"age":28,"name":"Phillip"} Name: Phillip Age: 28 

Aquí hay una biblioteca de muestra que también se llama en otro proyecto:


 namespace AnonyRecdOne open Newtonsoft.Json module AR = let serialize () = let phillip = {| name="Phillip"; age=28 |} JsonConvert.SerializeObject(phillip) 

 open AnonyRecdOne open Newtonsoft.Json [<EntryPoint>] let main _ = let str = AR.serialize () let phillip = JsonConvert.DeserializeObject<{|name: string; age: int|}>(str) printfn "Name: %s Age: %d" phillip.name phillip.age 

Esto puede facilitar las cosas para escenarios como datos livianos que pasan por una red en un sistema compuesto por microservicios.


Los registros anónimos se pueden combinar con otras definiciones de tipo.


Puede tener un modelo de datos en forma de árbol en su dominio, como el siguiente ejemplo:


 type FullName = { FirstName: string; LastName: string } type Employee = | Engineer of FullName | Manager of name: FullName * reports: Employee list | Executive of name: FullName * reports: Employee list * assistant: Employee 

Es típico ver casos modelados como tuplas con campos de unión con nombre, pero a medida que los datos se vuelven más complicados, puede extraer cada caso con registros:


 type FullName = { FirstName: string; LastName: string } type Employee = | Engineer of FullName | Manager of Manager | Executive of Executive and Manager = { Name: FullName; Reports: Employee list } and Executive = { Name: FullName; Reports: Employee list; Assistant: Employee } 

Esta definición recursiva ahora se puede acortar con registros anónimos si se adapta a su base de código:


 type FullName = { FirstName: string; LastName: string } type Employee = | Engineer of FullName | Manager of {| Name: FullName; Reports: Employee list |} | Executive of {| Name: FullName; Reports: Employee list; Assistant: Employee |} 

Al igual que con los ejemplos anteriores, esta técnica debe aplicarse con criterio y cuando corresponda a su escenario.


Los registros anónimos facilitan el uso de LINQ en F #


Los programadores de F # generalmente prefieren usar los combinadores List, Array y Sequence cuando trabajan con datos, pero a veces puede ser útil usar LINQ . Esto ha sido tradicionalmente un poco doloroso, ya que LINQ hace uso de tipos anónimos C #.


Con registros anónimos, puede usar métodos LINQ tal como lo haría con C # y tipos anónimos:


 open System.Linq let names = [ "Ana"; "Felipe"; "Emillia"] let nameGrouping = names.Select(fun n -> {| Name=n; FirstLetter=n.[0] |}) for ng in nameGrouping do printfn "%s has first letter %c" ng.Name ng.FirstLetter 

Esto imprime:


 Ana has first letter A Felipe has first letter F Emillia has first letter E 

Los registros anónimos facilitan el trabajo con Entity Framework y otros ORM


Los programadores de F # que usan expresiones de consulta de F # para interactuar con una base de datos deberían ver algunas mejoras menores en la calidad de vida con registros anónimos.


Por ejemplo, puede estar acostumbrado a usar tuplas para agrupar datos con una cláusula `select`:


 let q = query { for row in db.Status do select (row.StatusID, row.Name) } 

Pero esto da como resultado columnas con nombres como Item1 e Item2 que no son ideales. Antes de los registros anónimos, deberá declarar un tipo de registro y usarlo. Ahora no necesitas hacer eso:


 let q = query { for row in db.Status do select {| StatusID = row.StatusID; Name = row.Name |} } 

¡No es necesario especificar el tipo de registro por adelantado! Esto hace que las expresiones de consulta estén mucho más alineadas con el SQL real que modelan.


Los registros anónimos también le permiten evitar tener que crear tipos de objeto anónimo en consultas más avanzadas solo para crear una agrupación de datos ad-hoc para los fines de la consulta.


Los registros anónimos facilitan el uso de enrutamiento personalizado en ASP.NET Core


Es posible que ya esté utilizando ASP.NET Core con F #, pero puede haberse encontrado con una incomodidad al definir rutas personalizadas. Al igual que con los ejemplos anteriores, esto aún se podría hacer definiendo un tipo de registro por adelantado, pero los desarrolladores de F # lo han considerado innecesario. Ahora puedes hacerlo en línea:


 app.UseMvc(fun routes -> routes.MapRoute("blog","blog/{*article}", defaults={| controller="Blog"; action="Article" |}) |> ignore ) |> ignore 

Todavía no es ideal debido al hecho de que F # es estricto sobre los tipos de retorno (a diferencia de C #, donde no es necesario ignorar explícitamente las cosas que devuelven un valor). Sin embargo, esto le permite eliminar definiciones de registros previamente definidas que no tenían otro propósito que el de enviar datos a la tubería de middleware ASP.NET.


Copie y actualice expresiones con registros anónimos


Al igual que con los tipos de registro, puede usar la sintaxis de copiar y actualizar con registros anónimos:


 let data = {| X = 1; Y = 2 |} let expandedData = {| data with Z = 3 |} // Gives {| X=1; Y=2; Z=3 |} let stringifiedY = {| expandedData with Y="Hello!" |} // Gives {| X=1; Y="Hello!"; Z=3 |} 

La expresión original también puede ser un tipo de registro:


 type R = { X: int } let data = { X=1 } let data' = {| data with Y = 2 |} // Gives {| X=1; Y=2 |} 

También puede copiar datos ay desde referencia y estructurar registros anónimos:


 // Copy data from a reference record into a struct anonymous record type R1 = { X: int } let r1 = { X=1 } let data1 = struct {| r1 with Y=1 |} // Copy data from a struct record into a reference anonymous record [<Struct>] type R2 = { X: int } let r2 = { X=1 } let data2 = {| r1 with Y=1 |} 

El uso de expresiones de copiar y actualizar otorga a los registros anónimos un alto grado de flexibilidad cuando se trabaja con datos en F #.


Igualdad y coincidencia de patrones


Los registros anónimos son estructuralmente equiparables y comparables:


 {| a = 1+1 |} = {| a = 2 |} // true {| a = 1+1 |} > {| a = 1 |} // true 

Sin embargo, los tipos que se comparan deben tener la misma "forma":


 // error FS0001: Two anonymous record types have mismatched sets of field names '["a"]' and '["a"; "b"]' {| a = 1+1 |} = {| a = 2; b = 1|} 

Aunque puede igualar y comparar registros anónimos, no puede emparejar patrones sobre ellos. Esto es por dos razones:


  • Un patrón debe tener en cuenta cada campo de un registro anónimo, a diferencia de los tipos de registro. Esto se debe a que los registros anónimos no admiten subtipos estructurales, son tipos nominales.
  • No hay capacidad de tener patrones adicionales en una expresión de coincidencia de patrones, ya que cada patrón distinto implicaría un tipo de registro anónimo diferente.
  • El requisito de dar cuenta de cada campo en un registro anónimo haría un patrón más detallado que el uso de la notación de "punto".

En cambio, la sintaxis "punto" se utiliza para extraer valores de un registro anónimo. Esto siempre será tan detallado como si se usara la coincidencia de patrones, y en la práctica es probable que sea menos detallado debido a que no siempre se extraen todos los valores de un registro anónimo. Aquí le mostramos cómo trabajar con un ejemplo anterior donde los registros anónimos son parte de una unión discriminada:


 type Employee = | Engineer of FullName | Manager of {| Name: FullName; Reports: Employee list |} | Executive of {| Name: FullName; Reports: Employee list; Assistant: Employee |} let getFirstName e = match e with | Engineer fullName -> fullName.FirstName | Manager m -> m.Name.FirstName | Executive ex -> ex.Name.FirstName 

Actualmente hay una sugerencia abierta para permitir la coincidencia de patrones en registros anónimos en los contextos limitados que realmente podrían habilitarse. Si tiene un caso de uso propuesto, ¡use ese tema para discutirlo!


FSharp.Core adiciones


¡No sería otra versión de F # sin adiciones a la Biblioteca de F # Core!


Expansión ValueOption


El tipo ValueOption que se presenta en F # 4.5 ahora tiene algunas cosas más adjuntas al tipo:


  • El atributo DebuggerDisplay para ayudar con la depuración
  • Miembros IsNone , IsSome , None , Some , op_Implicit y ToString

Esto le da "paridad" con el tipo de opción.


Además, ahora hay un módulo ValueOption que contiene las mismas funciones que tiene el módulo `Option`:


 module ValueOption = [<CompiledName("IsSome")>] val inline isSome: voption:'T voption -> bool [<CompiledName("IsNone")>] val inline isNone: voption:'T voption -> bool [<CompiledName("DefaultValue")>] val defaultValue: value:'T -> voption:'T voption -> 'T [<CompiledName("DefaultWith")>] val defaultWith: defThunk:(unit -> 'T) -> voption:'T voption -> 'T [<CompiledName("OrElse")>] val orElse: ifNone:'T voption -> voption:'T voption -> 'T voption [<CompiledName("OrElseWith")>] val orElseWith: ifNoneThunk:(unit -> 'T voption) -> voption:'T voption -> 'T voption [<CompiledName("GetValue")>] val get: voption:'T voption -> 'T [<CompiledName("Count")>] val count: voption:'T voption -> int [<CompiledName("Fold")>] val fold<'T,'State> : folder:('State -> 'T -> 'State) -> state:'State -> voption:'T voption -> 'State [<CompiledName("FoldBack")>] val foldBack<'T,'State> : folder:('T -> 'State -> 'State) -> voption:'T voption -> state:'State -> 'State [<CompiledName("Exists")>] val exists: predicate:('T -> bool) -> voption:'T voption -> bool [<CompiledName("ForAll")>] val forall: predicate:('T -> bool) -> voption:'T voption -> bool [<CompiledName("Contains")>] val inline contains: value:'T -> voption:'T voption -> bool when 'T : equality [<CompiledName("Iterate")>] val iter: action:('T -> unit) -> voption:'T voption -> unit [<CompiledName("Map")>] val map: mapping:('T -> 'U) -> voption:'T voption -> 'U voption [<CompiledName("Map2")>] val map2: mapping:('T1 -> 'T2 -> 'U) -> voption1: 'T1 voption -> voption2: 'T2 voption -> 'U voption [<CompiledName("Map3")>] val map3: mapping:('T1 -> 'T2 -> 'T3 -> 'U) -> 'T1 voption -> 'T2 voption -> 'T3 voption -> 'U voption [<CompiledName("Bind")>] val bind: binder:('T -> 'U voption) -> voption:'T voption -> 'U voption [<CompiledName("Flatten")>] val flatten: voption:'T voption voption -> 'T voption [<CompiledName("Filter")>] val filter: predicate:('T -> bool) -> voption:'T voption -> 'T voption [<CompiledName("ToArray")>] val toArray: voption:'T voption -> 'T[] [<CompiledName("ToList")>] val toList: voption:'T voption -> 'T list [<CompiledName("ToNullable")>] val toNullable: voption:'T voption -> Nullable<'T> [<CompiledName("OfNullable")>] val ofNullable: value:Nullable<'T> -> 'T voption [<CompiledName("OfObj")>] val ofObj: value: 'T -> 'T voption when 'T : null [<CompiledName("ToObj")>] val toObj: value: 'T voption -> 'T when 'T : null 

Esto debería aliviar cualquier preocupación de que `ValueOption` sea el hermano extraño de` Option` que no tiene el mismo conjunto de funcionalidades.


tryExactlyOne para List, Array y Seq


Esta excelente función fue aportada por Grzegorz Dziadkiewicz . Así es como funciona:


 List.tryExactlyOne [] // None List.tryExactlyOne [1] // Some 1 List.tryExactlyOne [1; 2] // None Array.tryExactlyOne null // ArgumentNullException Array.tryExactlyOne [||] // None Array.tryExactlyOne [|1|] // Some 1 Array.tryExactlyOne [|1; 2|] // None Seq.tryExactlyOne null // ArgumentNullException Seq.tryExactlyOne (Seq.ofList []) // None Seq.tryExactlyOne (Seq.ofList [1]) // Some 1 Seq.tryExactlyOne (Seq.ofList [1; 2]) // None 

Terminando


Aunque la lista total de características en F # 4.6 no es enorme, ¡aún son bastante profundas! Lo alentamos a que pruebe F # 4.6 y nos deje comentarios para que podamos ajustar las cosas antes del lanzamiento completo. Como siempre, gracias a la comunidad de F # por sus contribuciones, tanto en el código como en la discusión del diseño, que nos ayudan a seguir avanzando en el lenguaje F #.


¡Salud y feliz pirateo!

Source: https://habr.com/ru/post/437666/


All Articles