Tutorial F#: Como criar um aplicativo F# full-stack

Publicados: 2022-03-11

Nos últimos anos, a programação funcional ganhou reputação como um paradigma particularmente rigoroso e produtivo. Não apenas as linguagens de programação funcionais estão ganhando atenção nas comunidades de programadores, mas muitas grandes empresas também estão começando a usar linguagens de programação funcionais para resolver problemas comerciais.

Por exemplo, o Walmart começou a usar Clojure, um dialeto Lisp funcional baseado em JVM, para sua infraestrutura de checkout; Jet.com, uma grande plataforma de comércio eletrônico (agora de propriedade do Walmart), usa F# para construir a maioria de seus microsserviços; e Jane Street, uma empresa comercial proprietária, usam principalmente o OCaml para construir seus algoritmos.

Hoje, vamos explorar a programação em F#. F# é uma das linguagens de programação funcional que está tendo uma adoção crescente devido à sua flexibilidade, forte integração .NET e alta qualidade das ferramentas disponíveis. Para os propósitos deste tutorial em F#, criaremos um servidor Web simples e um aplicativo móvel relacionado usando apenas F# para o front-end e o back-end.

Por que escolher F# e para que serve o F#?

Para o projeto de hoje, não usaremos nada além de F#. Existem várias razões para preferir F# como nossa linguagem de escolha:

  • Integração .NET: F# tem uma integração muito forte com o resto do mundo .NET e, portanto, acesso imediato a um grande ecossistema de bibliotecas bem suportadas e completamente documentadas para resolver uma ampla gama de tarefas de programação.
  • Concisão: F# é extremamente conciso devido ao seu poderoso sistema de inferência de tipos e sintaxe concisa. As tarefas de programação geralmente podem ser resolvidas de maneira muito mais elegante usando F# do que C# ou Java. O código F# pode parecer muito simplificado em comparação.
  • Ferramentas do desenvolvedor: O F# possui forte integração com o Visual Studio, que é um dos melhores IDEs para o ecossistema .NET. Para aqueles que trabalham em plataformas não Windows, há uma abundância de plugins no código do visual studio. Essas ferramentas tornam a programação em F# extremamente produtiva.

Eu poderia continuar falando sobre os benefícios de usar F#, mas sem mais delongas, vamos mergulhar!

A ideia por trás do nosso tutorial de F#

Nos Estados Unidos, existe um ditado popular: “São cinco horas em algum lugar” .

Em algumas partes do mundo, 17h é o horário mais cedo em que é socialmente aceitável tomar uma bebida ou uma xícara de chá tradicional.

Hoje, vamos construir um aplicativo baseado neste conceito. Vamos construir uma aplicação que, a qualquer momento, pesquisa os vários fusos horários, descobre onde são cinco horas e fornece essa informação ao utilizador.

A extremidade traseira

Configurando o Servidor Web

Começaremos fazendo o serviço de back-end que executa a função de pesquisa de fuso horário. Usaremos o Suave.IO para construir uma API JSON.

Ilustração do tutorial em F#: configuração do servidor web

O Suave.IO é um framework web intuitivo com um servidor web leve que permite que aplicativos web simples sejam codificados muito rapidamente.

Para começar, vá para o Visual Studio e inicie um novo projeto F# Console Application. Se essa opção não estiver disponível para você, talvez seja necessário instalar a funcionalidade F# com o Visual Studio Installer. Nomeie o projeto “FivePM”. Depois que seu aplicativo for criado, você deverá ver algo assim:

 [<EntryPoint>] let main argv = printfn "%A" argv 0 // return an integer exit code

Esta é uma parte muito simples do código inicial que imprime o argumento e sai com o código de status 0. Sinta-se à vontade para alterar a instrução print e experimentar várias funções do código. O formatador “%A” é um formatador especial que imprime a representação de string de qualquer tipo que você passar, então sinta-se à vontade para imprimir inteiros, floats ou mesmo tipos complexos. Uma vez que você esteja confortável com a sintaxe básica, é hora de instalar o Suave.

A maneira mais fácil de instalar o Suave é através do gerenciador de pacotes NuGet. Vá para Projeto -> Gerenciar pacotes NuGet e clique na guia Procurar. Procure Suave e clique em instalar. Depois de aceitar os pacotes para instalar, você deve estar pronto! Agora volte para a tela program.fs e estamos prontos para começar a construir o servidor.

Para começar a usar o Suave, precisamos primeiro importar o pacote. Na parte superior do seu programa, digite as seguintes instruções:

 open Suave open Suave.Operators open Suave.Filters open Suave.Successful

Isso importará os pacotes básicos necessários para construir um servidor web básico. Agora substitua o código principal pelo seguinte, que define um aplicativo simples e o serve na porta 8080:

 [<EntryPoint>] let main argv = // Define the port where you want to serve. We'll hardcode this for now. let port = 8080 // create an app config with the port let cfg = { defaultConfig with bindings = [ HttpBinding.createSimple HTTP "0.0.0.0" port]} // We'll define a single GET route at the / endpoint that returns "Hello World" let app = choose [ GET >=> choose [ path "/" >=> request (fun _ -> OK "Hello World!")] ] // Now we start the server startWebServer cfg app 0

O código deve parecer muito direto, mesmo que você não esteja familiarizado com a sintaxe do F# ou com a maneira do Suave de definir manipuladores de rota, o código deve ser bastante legível. Essencialmente, o aplicativo da web retorna com um status 200 e “Hello World!” string quando é atingido com uma solicitação GET na rota “/”. Vá em frente e execute o aplicativo (F5 no Visual Studio) e navegue até localhost:8080, e você deverá ver “Hello World!” na janela do seu navegador.

Refatorando o código do servidor

Agora temos um servidor web! Infelizmente, ele não faz muita coisa - então vamos dar algumas funcionalidades! Primeiro, vamos mover a funcionalidade do servidor web para outro lugar para que possamos construir algumas funcionalidades sem nos preocupar com o servidor web (nós iremos conectá-lo ao servidor web mais tarde). Defina uma função separada assim:

 // We'll use argv later :) let runWebServer argv = // Define the port where you want to serve. We'll hardcode this for now. let port = 8080 // create an app config with the port let cfg = { defaultConfig with bindings = [ HttpBinding.createSimple HTTP "0.0.0.0" port]} // We'll define a single GET route at the / endpoint that returns "Hello World" let app = choose [ GET >=> choose [ path "/" >=> request (fun _ -> OK "Hello World!")] ] // Now we start the server startWebServer cfg app

Agora altere a função main para o seguinte e certifique-se de que fizemos certo.

 [<EntryPoint>] let main argv = runWebServer argv 0

Aperte F5 e nosso “Hello World!” servidor deve funcionar como antes.

Obtendo fusos horários

Agora vamos construir a funcionalidade que determina o fuso horário onde são cinco horas. Queremos escrever algum código para percorrer todos os fusos horários e determinar o fuso horário mais próximo das 17h.

Buscando ilustração de fusos horários

Além disso, não queremos retornar um fuso horário muito próximo às 17h, mas um pouco antes (por exemplo, 16h58), porque, para fins desta demonstração, a premissa é que não pode ser antes das 5h. :00 pm, porém perto.

Vamos começar obtendo uma lista de fusos horários. Em F# isso é muito fácil, pois se integra muito bem com C#. Adicione “open System” na parte superior e altere seu aplicativo F# para isso:

 [<EntryPoint>] let main argv = // This gets all the time zones into a List-like object let tzs = TimeZoneInfo.GetSystemTimeZones() // Now we iterate through the list and print out the names of the timezones for tz in tzs do printfn "%s" tz.DisplayName 0

Execute o aplicativo e você deverá ver em seu console uma lista de todos os fusos horários, seus deslocamentos e seus nomes de exibição.

Criando e usando um tipo personalizado

Agora que temos a lista de fusos horários, podemos convertê-los em um tipo de dados personalizado que seja mais útil para nós, algo que contenha informações como o deslocamento UTC, hora local, a distância das 17h no horário local , etc. Para isso, vamos definir um tipo personalizado, logo acima da sua função principal:

 type TZInfo = {tzName: string; minDiff: float; localTime: string; utcOffset: float}

Agora podemos transformar as informações de fuso horário que obtivemos da última etapa em uma lista desses objetos TZInfo. Altere sua função principal assim:

 [<EntryPoint>] let main argv = // This gets all the time zones into a List-like object let tzs = TimeZoneInfo.GetSystemTimeZones() // List comprehension + type inference allows us to easily perform conversions let tzList = [ for tz in tzs do // convert the current time to the local time zone let localTz = TimeZoneInfo.ConvertTime(DateTime.Now, tz) // Get the datetime object if it was 5:00pm let fivePM = DateTime(localTz.Year, localTz.Month, localTz.Day, 17, 0, 0) // Get the difference between now local time and 5:00pm local time. let minDifference = (localTz - fivePM).TotalMinutes yield { tzName=tz.StandardName; minDiff=minDifference; localTime=localTz.ToString("hh:mm tt"); utcOffset=tz.BaseUtcOffset.TotalHours; } ] printfn "%A" tzList.Head 0

E você deve ver o objeto tzInfo para a hora padrão da linha de data impressa na tela.

Classificação e filtragem e tubulação, Oh meu Deus!

Agora que temos uma lista desses objetos tzInfo, podemos filtrar e classificar esses objetos para encontrar o fuso horário em que está 1) depois das 17h e 2) mais próximo das 17h dos fusos horários em 1). Altere sua função principal assim:

 [<EntryPoint>] let main argv = // This gets all the time zones into a List-like object let tzs = TimeZoneInfo.GetSystemTimeZones() // List comprehension + type inference allows us to easily perform conversions let tzList = [ for tz in tzs do // convert the current time to the local time zone let localTz = TimeZoneInfo.ConvertTime(DateTime.Now, tz) // Get the datetime object if it was 5:00pm let fivePM = DateTime(localTz.Year, localTz.Month, localTz.Day, 17, 0, 0) // Get the difference between now local time and 5:00pm local time. let minDifference = (localTz - fivePM).TotalMinutes yield { tzName=tz.StandardName; minDiff=minDifference; localTime=localTz.ToString("hh:mm tt"); utcOffset=tz.BaseUtcOffset.TotalHours; } ] // We use the pipe operator to chain functiona calls together let closest = tzList // filter so that we only get tz after 5pm |> List.filter (fun (i:TZInfo) -> i.minDiff >= 0.0) // sort by minDiff |> List.sortBy (fun (i:TZInfo) -> i.minDiff) // Get the first item |> List.head printfn "%A" closest

E agora devemos ter o fuso horário que estamos procurando.

Refatorando o Timezone Getter para sua própria função

Agora vamos refatorar o código para sua própria função para que possamos usá-lo mais tarde. Defina uma função assim:

 // the function takes uint as input, and we represent that as "()" let getClosest () = // This gets all the time zones into a List-like object let tzs = TimeZoneInfo.GetSystemTimeZones() // List comprehension + type inference allows us to easily perform conversions let tzList = [ for tz in tzs do // convert the current time to the local time zone let localTz = TimeZoneInfo.ConvertTime(DateTime.Now, tz) // Get the datetime object if it was 5:00pm let fivePM = DateTime(localTz.Year, localTz.Month, localTz.Day, 17, 0, 0) // Get the difference between now local time and 5:00pm local time. let minDifference = (localTz - fivePM).TotalMinutes yield { tzName=tz.StandardName; minDiff=minDifference; localTime=localTz.ToString("hh:mm tt"); utcOffset=tz.BaseUtcOffset.TotalHours; } ] // We use the pipe operator to chain function calls together tzList // filter so that we only get tz after 5pm |> List.filter (fun (i:TZInfo) -> i.minDiff >= 0.0) // sort by minDiff |> List.sortBy (fun (i:TZInfo) -> i.minDiff) // Get the first item |> List.head And our main function can just be: [<EntryPoint>] let main argv = printfn "%A" <| getClosest() 0

Execute o código e você deverá ver a mesma saída de antes.

JSON Codificando os Dados de Retorno

Agora que podemos obter os dados do fuso horário, podemos transformar as informações em JSON e servi-las por meio de nosso aplicativo. Isso é bem simples, graças ao pacote JSON.NET da NewtonSoft. Retorne ao gerenciador de pacotes NuGet, localize Newtonsoft.Json e instale o pacote. Agora retorne ao Program.fs e faça uma pequena alteração em nossa função principal:

 [<EntryPoint>] let main argv = printfn "%s" <| JsonConvert.SerializeObject(getClosest()) 0

Execute o código agora e, em vez do objeto TZInfo, você verá o JSON impresso em seu console.

Conectando as informações de fuso horário à API JSON

É muito simples conectar isso à nossa API JSON. Basta fazer as seguintes alterações em sua função runWebServer:

 // We'll use argv later :) let runWebServer argv = // Define the port where you want to serve. We'll hardcode this for now. let port = 8080 // create an app config with the port let cfg = { defaultConfig with bindings = [ HttpBinding.createSimple HTTP "0.0.0.0" port]} // We'll define a single GET route at the / endpoint that returns "Hello World" let app = choose [ GET >=> choose [ // We are getting the closest time zone, converting it to JSON, then setting the MimeType path "/" >=> request (fun _ -> OK <| JsonConvert.SerializeObject(getClosest())) >=> setMimeType "application/json; charset=utf-8" ] ] // Now we start the server startWebServer cfg app

Execute o aplicativo e navegue até localhost:8080. Você deve ver o JSON na janela do seu navegador.

Implantando o servidor

Agora que temos o servidor da API JSON, podemos implantá-lo para que fique acessível na Internet. Uma das maneiras mais fáceis de implantar esse aplicativo é por meio do Serviço de Aplicativo do Microsoft Azure, que pode ser entendido como um serviço IIS gerenciado. Para implantar no serviço de aplicativo do Azure, vá para https://portal.azure.com e vá para o serviço de aplicativo. Crie um novo aplicativo e navegue até o centro de implantação em seu portal. O portal pode ser um pouco complicado se for sua primeira vez, portanto, se você tiver problemas, consulte um dos muitos tutoriais disponíveis para usar o Serviço de Aplicativo.

Você deve ver uma variedade de opções para implantação. Você pode usar o que quiser, mas para simplificar, podemos usar a opção FTP.

O serviço de aplicativo procura um arquivo web.config na raiz do seu aplicativo para saber como executá-lo. Como nosso servidor web é um aplicativo de console essencial, podemos publicar o aplicativo e integrá-lo ao servidor IIS usando HttpPlatformHandler. No Visual Studio, adicione um arquivo XML ao seu projeto e nomeie-o como web.config. Preencha-o com o seguinte XML:

 <?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <handlers> <remove name="httpplatformhandler" /> <add name="httpplatformhandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified"/> </handlers> <httpPlatform stdoutLogEnabled="true" stdoutLogFile="suave.log" startupTimeLimit="20" processPath=".\publish\FivePM.exe" arguments="%HTTP_PLATFORM_PORT%"/> </system.webServer> </configuration>

Conecte-se ao servidor FTP usando as credenciais obtidas no centro de implantação (você precisará clicar na opção FTP). Mova o web.config para a pasta wwwroot do site FTP do serviço de aplicativo.

Agora queremos construir e publicar nosso aplicativo, mas antes disso, precisamos fazer uma pequena alteração no código do servidor. Vá para sua função runServer e altere as 3 primeiras linhas para o seguinte:

 let runWebServer (argv:string[]) = // Define the port where you want to serve. We'll hardcode this for now. let port = if argv.Length = 0 then 8080 else (int argv.[0])

O que permite que o aplicativo veja o argumento passado e use o primeiro argumento como o número da porta em vez de ter a porta codificada para 8080. Na configuração da web, passamos o %HTTP_PLATFORM_PORT% como o primeiro argumento, então deve ser definido.

Crie o aplicativo no modo de lançamento, publique o aplicativo e copie a pasta publicada para o wwwroot. Reinicie o aplicativo e você deverá ver o resultado da API JSON no site *.azurewebsites.net .

Agora nosso aplicativo está implantado!

O Front-End

Ilustração de front-end em F#

Agora que temos o servidor implantado, podemos construir um front-end. Para o front-end, construiremos um aplicativo Android usando Xamarin e F#. Essa pilha, como nosso ambiente de back-end, desfruta de integração profunda com o Visual Studio. Claro, o ecossistema F# suporta algumas opções de desenvolvimento de front-end (WebSharper, Fable/Elmish, Xamarin.iOS, DotLiquid etc), mas por uma questão de brevidade, vamos desenvolver apenas usando Xamarin.Android para este post e deixar -los para postagens futuras.

Configurando

Para configurar o aplicativo Android, inicie um novo projeto e selecione a opção Xamarin Android. Certifique-se de ter as ferramentas de desenvolvimento Android instaladas. Depois que o projeto estiver configurado, você deverá ver algo assim em seu arquivo de código principal.

 [<Activity (Label = "FivePMFinder", MainLauncher = true, Icon = "@mipmap/icon")>] type MainActivity () = inherit Activity () let mutable count:int = 1 override this.OnCreate (bundle) = base.OnCreate (bundle) // Set our view from the "main" layout resource this.SetContentView (Resources.Layout.Main) // Get our button from the layout resource, and attach an event to it let button = this.FindViewById<Button>(Resources.Id.myButton) button.Click.Add (fun args -> button.Text <- sprintf "%d clicks!" count count <- count + 1 )

Este é o código inicial para F# Android Xamarin. O código atualmente apenas acompanha quantas vezes um botão foi clicado e exibe o valor da contagem atual. Você pode vê-lo funcionar pressionando F5 para iniciar o emulador e iniciar o aplicativo no modo de depuração.

Adicionando componentes de interface do usuário

Vamos adicionar alguns componentes de interface do usuário e torná-los mais úteis. Abra o recurso/layouts e navegue até Main.axml.

Você deverá ver uma representação visual do layout da atividade principal. Você pode editar os vários elementos da interface do usuário clicando nos elementos. Você pode adicionar elementos acessando a caixa de ferramentas e selecionando o elemento que deseja adicionar. Renomeie o botão e adicione um textView abaixo do botão. A representação XML do seu AXML deve ser semelhante a esta:

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:andro android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android: android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/fivePM" /> <TextView android:text="" android:textAppearance="?android:attr/textAppearanceMedium" android:layout_width="match_parent" android:layout_height="wrap_content" android: /> </LinearLayout>

O AXML faz referência ao arquivo de recursos de strings, então abra seu resources/values/strings.xml e faça as seguintes alterações:

 <?xml version="1.0" encoding="utf-8"?> <resources> <string name="fivePM">It\'s 5PM Somewhere!</string> <string name="app_name">5PM Finder</string> </resources>

Agora construímos o AXML front-end. Agora vamos conectá-lo a algum código. Navegue até MainActivity.fs e faça as seguintes alterações em sua função onCreate:

 base.OnCreate (bundle) // Set our view from the "main" layout resource this.SetContentView (Resources.Layout.Main) // Get our button from the layout resource, and attach an event to it let button = this.FindViewById<Button>(Resources.Id.myButton) let txtView = this.FindViewById<TextView>(Resources.Id.textView1); button.Click.Add (fun args -> let webClient = new WebClient() txtView.Text <- webClient.DownloadString("https://fivepm.azurewebsites.net/") )

Substitua fivepm.azurewebsites.net pela URL de sua própria implantação de API JSON. Execute o aplicativo e clique no botão no emulador. Daqui a pouco, você deverá ver o retorno da API JSON com o resultado da sua API.

Analisando JSON

Estamos quase lá! No momento, nosso aplicativo está exibindo o JSON bruto e é bastante ilegível. A próxima etapa, então, é analisar o JSON e gerar uma string mais legível. Para analisar JSON, podemos usar a biblioteca Newtonsoft.JSON do servidor.

Navegue até o gerenciador de pacotes NuGet e pesquise Newtonsoft.JSON. Instale e volte para o arquivo MainActivity.fs. Importe-o adicionando “open Newtonsoft.Json”.

Agora adicione o tipo TZInfo ao projeto. Poderíamos reutilizar o TZInfo do servidor, mas como na verdade precisamos apenas de dois dos campos, podemos definir um tipo personalizado aqui:

 type TZInfo = { tzName:string localTime: string }

Adicione a definição de tipo acima da função principal e agora altere a função principal assim:

 button.Click.Add (fun args -> let webClient = new WebClient() let tzi = JsonConvert.DeserializeObject<TZInfo>(webClient.DownloadString("https://fivepm.azurewebsites.net/")) txtView.Text <- sprintf "It's (about) 5PM in the\n\n%s Timezone! \n\nSpecifically, it is %s there" tzi.tzName tzi.localTime )

Agora, o resultado da API JSON é desserializado no objeto TZInfo e usado para construir uma string. Execute o aplicativo e clique no botão. Você deve ver a string formatada aparecer na tela.

Embora esse aplicativo seja muito simples e talvez não refinado, criamos um aplicativo móvel que consome e F# JSON API, transforma os dados e os exibe para o usuário. E fizemos tudo em F#. Sinta-se à vontade para brincar com os vários controles e ver se você pode melhorar o design.

E aí temos que! Um aplicativo móvel F# simples e uma API JSON F# e informa ao usuário onde são cinco horas.

Empacotando

Hoje passamos pela construção de uma API Web simples e um aplicativo Android simples usando apenas F#, demonstrando a expressividade da linguagem F# e a força do ecossistema F#. No entanto, mal arranhamos a superfície do desenvolvimento em F#, então escreverei mais alguns posts para desenvolver o que discutimos hoje. Além disso, espero que este post tenha inspirado você a criar seus próprios aplicativos em F#!

Você pode encontrar o código que usamos para este tutorial no GitHub.