
Temos o prazer de anunciar que o Visual Studio 2019 enviará uma nova versão do F # quando for lançado: F # 4.6!
O F # 4.6 é uma atualização menor para o idioma F #, tornando-o um lançamento de ponto "verdadeiro". Como nas versões anteriores do F #, o F # 4.6 foi desenvolvido inteiramente por meio de um processo RFC (solicitações de comentários) aberto. A comunidade F # ofereceu feedback muito detalhado nas discussões para esta versão do idioma. Você pode ver todos os RFCs que correspondem a esta versão aqui:
Esta postagem detalhará o conjunto de recursos e como começar.
Original no blogComeçar
Primeiro, instale:
Em seguida, atualize sua dependência do FSharp.Core para FSharp.Core 4.6 (ou superior). Se você estiver usando o Visual Studio, poderá fazer isso com a interface do usuário do NuGet Package Management. Se você não estiver usando o Visual Studio, ou preferir editar arquivos de projeto manualmente, adicione-o ao arquivo de projeto:
<ItemGroup> <PackageReference Update="FSharp.Core" Version="4.6.0" /> </ItemGroup>
Depois de instalar os bits necessários, você pode usar o F # 4.6 com Visual Studio , Visual Studio para Mac ou Visual Studio Code com Ionide .
Registros anônimos
Além de várias correções de bugs, a única alteração de idioma no F # 4.6 é a introdução dos tipos de Registro Anônimo .
Uso básico
De uma perspectiva F #, os Registros Anônimos são tipos de registros F # que não têm nomes explícitos e podem ser declarados em um estágio ad-hoc. Embora seja improvável que alterem fundamentalmente a maneira como você escreve código F #, eles preenchem muitas lacunas menores encontradas pelos programadores ao longo do tempo e podem ser usados para manipulação sucinta de dados que não era possível anteriormente.
Eles são bastante fáceis de usar. Por exemplo, aqui como você pode interagir com uma função que produz um 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
No entanto, eles podem ser usados para mais do que apenas contêineres de dados básicos. A seguir, expande a amostra anterior para usar uma função de impressão mais segura para 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
Se você tentar chamar `printCircleStats` com um registro anônimo que tenha os mesmos tipos de dados subjacentes, mas rótulos diferentes, ele falhará ao compilar:
printCircleStats r {| Diameter=2.0; Area=4.0; MyCircumference=12.566371 |}
É exatamente assim que os tipos de registro F # funcionam, exceto que tudo foi declarado ad-hoc, e não antecipadamente. Isso tem vantagens e desvantagens, dependendo da sua situação específica, por isso recomendamos o uso criterioso de registros anônimos, em vez de substituir todas as suas declarações iniciais de registros F #.
Estruturar registros anônimos
Os registros anônimos também podem ser estruturas usando a palavra-chave 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
Você pode chamar uma função que usa um registro anônimo struct pode ser feita explicitamente da seguinte maneira:
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 |}
Ou você pode usar a "inferência da estrutura" para eliminar o `struct` no site da chamada:
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 |}
Isso tratará a instância do registro anônimo que você criou como se fosse uma estrutura.
Observe que o inverso não é verdadeiro:
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
Atualmente, não é possível definir os tipos de registro anônimo IsByRefLike ou IsReadOnly struct. Há uma sugestão de linguagem que propõe esse aprimoramento, mas devido a peculiaridades na sintaxe, ele ainda está em discussão.
Levando as coisas adiante
Registros anônimos podem ser usados em um conjunto mais amplo de contextos mais avançados.
Registros anônimos são serializáveis
Você pode serializar e desserializar 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
Isso gera o que você pode esperar:
{"age":28,"name":"Phillip"} Name: Phillip Age: 28
Aqui está uma biblioteca de exemplo que também é chamada em outro projeto:
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
Isso pode facilitar as coisas para cenários como dados leves passando por uma rede em um sistema composto por microsserviços.
Registros anônimos podem ser combinados com outras definições de tipo
Você pode ter um modelo de dados semelhante a uma árvore em seu domínio, como o exemplo a seguir:
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
É comum ver casos modelados como tuplas com campos de união nomeados, mas à medida que os dados ficam mais complicados, você pode extrair cada caso com 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 }
Agora, essa definição recursiva pode ser reduzida com registros anônimos se ela se adequar à sua 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 |}
Como nos exemplos anteriores, essa técnica deve ser aplicada criteriosamente e quando aplicável ao seu cenário.
Registros anônimos facilitam o uso do LINQ em F #
Os programadores de F # geralmente preferem usar os combinadores List, Array e Sequence ao trabalhar com dados, mas às vezes pode ser útil usar o LINQ . Tradicionalmente, isso tem sido um pouco doloroso, já que o LINQ faz uso de tipos anônimos C #.
Com registros anônimos, você pode usar os métodos LINQ da mesma maneira que faria com tipos C # e 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
Isso imprime:
Ana has first letter A Felipe has first letter F Emillia has first letter E
Registros anônimos facilitam o trabalho com o Entity Framework e outros ORMs
Os programadores de F # que usam expressões de consulta de F # para interagir com um banco de dados devem observar pequenas melhorias na qualidade de vida com registros anônimos.
Por exemplo, você pode estar acostumado a usar tuplas para agrupar dados com uma cláusula `select`:
let q = query { for row in db.Status do select (row.StatusID, row.Name) }
Mas isso resulta em colunas com nomes como Item1 e Item2 que não são ideais. Antes dos registros anônimos, você precisaria declarar um tipo de registro e usá-lo. Agora você não precisa fazer isso:
let q = query { for row in db.Status do select {| StatusID = row.StatusID; Name = row.Name |} }
Não é necessário especificar o tipo de registro antecipadamente! Isso torna as expressões de consulta muito mais alinhadas com o SQL real que elas modelam.
Os registros anônimos também permitem evitar a criação de tipos AnonymousObject em consultas mais avançadas, apenas para criar um agrupamento ad-hoc de dados para os fins da consulta.
Registros anônimos facilitam o uso do roteamento personalizado no ASP.NET Core
Você já pode estar usando o ASP.NET Core com F #, mas pode ter encontrado um constrangimento ao definir rotas personalizadas. Como nos exemplos anteriores, isso ainda pode ser feito definindo um tipo de registro antecipadamente, mas isso costuma ser visto como desnecessário pelos desenvolvedores de F #. Agora você pode fazer isso inline:
app.UseMvc(fun routes -> routes.MapRoute("blog","blog/{*article}", defaults={| controller="Blog"; action="Article" |}) |> ignore ) |> ignore
Ainda não é o ideal devido ao fato de o F # ser estrito sobre os tipos de retorno (diferente do C #, onde você não precisa ignorar explicitamente as coisas que retornam um valor). No entanto, isso permite remover as definições de registro definidas anteriormente que não tinham outro objetivo, além de permitir o envio de dados ao pipeline de middleware do ASP.NET.
Copie e atualize expressões com registros anônimos
Assim como nos tipos de registro, você pode usar a sintaxe de copiar e atualizar com registros anônimos:
let data = {| X = 1; Y = 2 |} let expandedData = {| data with Z = 3 |}
A expressão original também pode ser um tipo de registro:
type R = { X: int } let data = { X=1 } let data' = {| data with Y = 2 |}
Você também pode copiar dados para e da referência e estruturar registros anônimos:
O uso de expressões de copiar e atualizar oferece aos registros anônimos um alto grau de flexibilidade ao trabalhar com dados em F #.
Igualdade e correspondência de padrões
Registros anônimos são estruturalmente equiparáveis e comparáveis:
{| a = 1+1 |} = {| a = 2 |}
No entanto, os tipos que estão sendo comparados devem ter a mesma "forma":
Embora você possa equiparar e comparar registros anônimos, não é possível fazer correspondência de padrões sobre eles. Isso ocorre por dois motivos:
- Um padrão deve ser responsável por todos os campos de um registro anônimo, diferentemente dos tipos de registro. Isso ocorre porque os registros anônimos não suportam subtipagem estrutural - são tipos nominais.
- Não há capacidade de ter padrões adicionais em uma expressão de correspondência de padrão, pois cada padrão distinto implicaria um tipo de registro anônimo diferente.
- O requisito de contabilizar todos os campos em um registro anônimo tornaria um padrão mais detalhado do que o uso da notação "ponto".
Em vez disso, a sintaxe “ponto” é usada para extrair valores de um registro anônimo. Isso sempre será tão detalhado quanto se a correspondência de padrões fosse usada e, na prática, é provável que seja menos detalhada, porque nem sempre é possível extrair todo valor de um registro anônimo. Veja como trabalhar com um exemplo anterior, em que registros anônimos fazem parte de uma união 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
Atualmente, existe uma sugestão aberta para permitir a correspondência de padrões em registros anônimos nos contextos limitados de que eles realmente poderiam ser ativados. Se você tem um caso de uso proposto, use esse problema para discuti-lo!
Adições FSharp.Core
Não seria outra versão do F # sem acréscimos à Biblioteca do F # Core!
Expansão ValueOption
O tipo ValueOption introduzido no F # 4.5 agora tem mais alguns itens anexados ao tipo:
- O atributo DebuggerDisplay para ajudar na depuração
- Membros IsNone , IsSome , None , Some , op_Implicit e ToString
Isso confere "paridade" ao tipo de opção.
Além disso, agora existe um módulo ValueOption contendo as mesmas funções que o módulo `Option` possui:
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
Isso deve aliviar qualquer preocupação de que `ValueOption` seja o irmão estranho da` Option` que não recebe o mesmo conjunto de funcionalidades.
tryExactlyOne para List, Array e Seq
Esta boa função foi contribuída por Grzegorz Dziadkiewicz . Veja como funciona:
List.tryExactlyOne []
Finalizando
Embora a lista total de recursos no F # 4.6 não seja enorme, eles ainda são bastante profundos! Nós encorajamos você a experimentar o F # 4.6 e nos deixar um feedback para que possamos ajustar as coisas antes do lançamento completo. Como sempre, obrigado à comunidade F # por suas contribuições - tanto na discussão de código quanto na de design - que nos ajudam a continuar avançando no idioma F #.
Felicidades e hackers felizes!