Tutorial de F#: Cómo crear una aplicación F# de pila completa
Publicado: 2022-03-11En los últimos años, la programación funcional se ha ganado la reputación de ser un paradigma particularmente riguroso y productivo. Los lenguajes de programación funcionales no solo están ganando atención dentro de las comunidades de programadores, sino que muchas grandes empresas también están comenzando a utilizar lenguajes de programación funcionales para resolver problemas comerciales.
Por ejemplo, Walmart ha comenzado a usar Clojure, un dialecto Lisp funcional basado en JVM, para su infraestructura de pago; Jet.com, una gran plataforma de comercio electrónico (ahora propiedad de Walmart), usa F# para construir la mayoría de sus microservicios; y Jane Street, una firma comercial propietaria, utilizan principalmente OCaml para construir sus algoritmos.
Hoy exploraremos la programación en F#. F# es uno de los lenguajes de programación funcional que está experimentando una adopción cada vez mayor debido a su flexibilidad, sólida integración con .NET y alta calidad de las herramientas disponibles. A los efectos de este tutorial de F#, crearemos un servidor web simple y una aplicación móvil relacionada usando solo F# tanto para el front-end como para el back-end.
¿Por qué elegir F# y para qué se usa F#?
Para el proyecto de hoy, usaremos nada más que F#. Hay varias razones para preferir F# como nuestro lenguaje de elección:
- Integración de .NET: F# tiene una integración muy estrecha con el resto del mundo de .NET y, por lo tanto, acceso rápido a un gran ecosistema de bibliotecas bien documentadas y bien respaldadas para resolver una amplia gama de tareas de programación.
- Concisión: F# es extremadamente conciso debido a su potente sistema de inferencia de tipos y su concisa sintaxis. Las tareas de programación a menudo se pueden resolver de forma mucho más elegante con F# que con C# o Java. El código F# puede parecer muy simplificado en comparación.
- Herramientas para desarrolladores: F# disfruta de una sólida integración con Visual Studio, que es uno de los mejores IDE para el ecosistema .NET. Para aquellos que trabajan en plataformas que no son de Windows, existe una gran cantidad de complementos en el código de Visual Studio. Estas herramientas hacen que la programación en F# sea extremadamente productiva.
Podría continuar con los beneficios de usar F#, pero sin más preámbulos, ¡vamos a sumergirnos!
La idea detrás de nuestro tutorial de F#
En Estados Unidos, existe un dicho popular: “Son las cinco en algún lugar” .
En algunas partes del mundo, las 5:00 p. m. es la hora más temprana en que es socialmente aceptable tomar una copa o una taza de té tradicional.
Hoy construiremos una aplicación basada en este concepto. Construiremos una aplicación que, en un momento dado, busque en las distintas zonas horarias, averigüe dónde son las cinco y proporcione esa información al usuario.
la parte de atrás
Configuración del servidor web
Comenzaremos creando el servicio de back-end que realiza la función de búsqueda de zona horaria. Usaremos Suave.IO para construir una API JSON.
Suave.IO es un marco web intuitivo con un servidor web liviano que permite codificar aplicaciones web simples muy rápidamente.
Para comenzar, vaya a Visual Studio e inicie un nuevo proyecto de aplicación de consola F#. Si esta opción no está disponible para usted, es posible que deba instalar la funcionalidad de F# con Visual Studio Installer. Nombra el proyecto "FivePM". Una vez creada su aplicación, debería ver algo como esto:
[<EntryPoint>] let main argv = printfn "%A" argv 0 // return an integer exit code
Esta es una pieza muy simple de código de inicio que imprime el argumento y sale con el código de estado 0. Siéntase libre de cambiar la declaración de impresión y experimentar con varias funciones del código. El formateador "%A" es un formateador especial que imprime la representación de cadena de cualquier tipo que pase, así que siéntase libre de imprimir números enteros, flotantes o incluso tipos complejos. Una vez que se sienta cómodo con la sintaxis básica, es hora de instalar Suave.
La forma más fácil de instalar Suave es a través del administrador de paquetes NuGet. Vaya a Proyecto -> Administrar paquetes de NuGet y haga clic en la pestaña de exploración. Busque Suave y haga clic en instalar. Una vez que acepte los paquetes para instalar, ¡debe estar listo! Ahora regrese a su pantalla program.fs y estamos listos para comenzar a construir el servidor.
Para comenzar a usar Suave, primero necesitaremos importar el paquete. En la parte superior de su programa, escriba las siguientes declaraciones:
open Suave open Suave.Operators open Suave.Filters open Suave.Successful
Esto importará los paquetes básicos necesarios para construir un servidor web básico. Ahora reemplace el código principal con lo siguiente, que define una aplicación simple y la sirve en el puerto 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
El código debe parecer muy sencillo, incluso si no está familiarizado con la sintaxis de F# o la forma en que Suave define los controladores de ruta, el código debe ser bastante legible. Esencialmente, la aplicación web regresa con un estado 200 y "¡Hola mundo!" cadena cuando recibe una solicitud GET en la ruta "/". Continúe y ejecute la aplicación (F5 en Visual Studio) y navegue hasta localhost: 8080, y debería ver "¡Hola mundo!" en la ventana de su navegador.
Refactorización del código del servidor
¡Ya tenemos un servidor web! Desafortunadamente, no hace mucho, ¡así que vamos a darle algo de funcionalidad! Primero, movamos la funcionalidad del servidor web a otro lugar para que construyamos alguna funcionalidad sin preocuparnos por el servidor web (lo conectaremos al servidor web más tarde). Defina una función separada así:
// 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
Ahora cambie la función principal a la siguiente y asegúrese de que lo hicimos bien.
[<EntryPoint>] let main argv = runWebServer argv 0
Presiona F5 y nuestro "¡Hola mundo!" El servidor debería funcionar como antes.
Obtener zonas horarias
Ahora, desarrollemos la funcionalidad que determina la zona horaria donde son las cinco. Queremos escribir un código para iterar a través de todas las zonas horarias y determinar la zona horaria más cercana a las 5:00 p. m.
Además, en realidad no queremos devolver una zona horaria que esté muy cerca de las 5:00 p. m. pero que esté un poco antes (por ejemplo, las 4:58 p. :00 pm, por cerca que sea.
Comencemos por obtener una lista de zonas horarias. En F# esto es muy fácil ya que se integra muy bien con C#. Agregue "Sistema abierto" en la parte superior y cambie su aplicación F# a esto:
[<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
Ejecute la aplicación y debería ver en su consola una lista de todas las zonas horarias, sus compensaciones y su nombre para mostrar.
Creación y uso de un tipo personalizado
Ahora que tenemos la lista de zonas horarias, podemos convertirlas a un tipo de datos personalizado que nos sea más útil, algo que contenga información como el desplazamiento UTC, la hora local, qué tan lejos está de las 5:00 p. m. en hora local. , etc. Con ese fin, definamos un tipo personalizado, justo encima de su función principal:
type TZInfo = {tzName: string; minDiff: float; localTime: string; utcOffset: float}
Ahora podemos transformar la información de la zona horaria que obtuvimos en el último paso en una lista de estos objetos TZInfo. Cambia tu función principal de esta manera:
[<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
Y debería ver el objeto tzInfo para la hora estándar de la línea de fecha impreso en su pantalla.
Clasificación, filtrado y canalización, ¡Dios mío!
Ahora que tenemos una lista de estos objetos tzInfo, podemos filtrar y ordenar estos objetos para encontrar la zona horaria donde está 1) después de las 5:00 p. m. y 2) más cercana a las 5:00 p. m. de las zonas horarias en 1). Cambia tu función principal así:
[<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
Y ahora deberíamos tener la zona horaria que estamos buscando.
Refactorización del captador de zona horaria a su propia función
Ahora vamos a refactorizar el código a su propia función para que podamos usarlo más tarde. Defina una función así:
// 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
Ejecute el código y debería ver el mismo resultado que antes.

Codificación JSON de los datos de retorno
Ahora que podemos obtener los datos de la zona horaria, podemos transformar la información en JSON y servirla a través de nuestra aplicación. Esto es bastante simple, gracias al paquete JSON.NET de NewtonSoft. Vuelva a su administrador de paquetes NuGet y busque Newtonsoft.Json e instale el paquete. Ahora regrese a Program.fs y haga un pequeño cambio en nuestra función principal:
[<EntryPoint>] let main argv = printfn "%s" <| JsonConvert.SerializeObject(getClosest()) 0
Ejecute el código ahora y en lugar del objeto TZInfo, debería ver el JSON impreso en su consola.
Conexión de la información de la zona horaria a la API de JSON
Es muy simple conectar esto a nuestra API JSON. Simplemente realice los siguientes cambios en su función 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
Ejecute la aplicación y navegue hasta localhost:8080. Debería ver el JSON en la ventana de su navegador.
Implementación del servidor
Ahora que tenemos el servidor API JSON, podemos implementarlo para que sea accesible en Internet. Una de las formas más fáciles de implementar esta aplicación es a través del Servicio de aplicaciones de Microsoft Azure, que puede entenderse como un servicio de IIS administrado. Para implementar en Azure App Service, diríjase a https://portal.azure.com y vaya a App Service. Cree una nueva aplicación y navegue hasta el centro de implementación en su portal. El portal puede ser un poco abrumador si es su primera vez, así que si tiene problemas, asegúrese de consultar uno de los muchos tutoriales que existen para usar App Service.
Debería ver una variedad de opciones para la implementación. Puede usar cualquiera que desee, pero por simplicidad, podemos usar la opción FTP.
El servicio de aplicaciones busca un archivo web.config en la raíz de su aplicación para saber cómo ejecutarla. Dado que nuestro servidor web es una aplicación de consola esencial, podemos publicar la aplicación e integrarla con el servidor IIS mediante HttpPlatformHandler. En Visual Studio, agregue un archivo XML a su proyecto y asígnele el nombre web.config. Rellénalo con el siguiente 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>
Conéctese al servidor FTP utilizando las credenciales obtenidas del centro de implementación (deberá hacer clic en la opción FTP). Mueva web.config a la carpeta wwwroot de su sitio FTP de servicio de aplicaciones.
Ahora queremos compilar y publicar nuestra aplicación, pero antes de hacerlo, debemos realizar un pequeño cambio en el código del servidor. Vaya a su función runServer y cambie las primeras 3 líneas a lo siguiente:
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])
Lo que permite que la aplicación mire el argumento pasado y use el primer argumento como el número de puerto en lugar de tener el puerto codificado en 8080. En la configuración web, pasamos %HTTP_PLATFORM_PORT% como el primer argumento, por lo que debe establecerse.
Compile la aplicación en modo de publicación, publíquela y copie la carpeta publicada en wwwroot. Reinicie la aplicación y debería ver el resultado de la API de JSON en el sitio *.azurewebsites.net
.
¡Ahora nuestra aplicación está implementada!
el frente
Ahora que tenemos el servidor implementado, podemos construir un front-end. Para el front-end, construiremos una aplicación de Android usando Xamarin y F#. Esta pila, como nuestro entorno de back-end, disfruta de una profunda integración con Visual Studio. Por supuesto, el ecosistema de F# admite bastantes opciones de desarrollo front-end (WebSharper, Fable/Elmish, Xamarin.iOS, DotLiquid, etc.), pero en aras de la brevedad, solo desarrollaremos usando Xamarin.Android para esta publicación y dejaremos ellos para futuras publicaciones.
Configuración
Para configurar la aplicación de Android, inicie un nuevo proyecto y seleccione la opción Xamarin Android. Asegúrese de tener instaladas las herramientas de desarrollo de Android. Una vez que el proyecto esté configurado, debería ver algo como esto en su archivo 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 es el código de inicio para F# Android Xamarin. Actualmente, el código solo realiza un seguimiento de cuántas veces se hizo clic en un botón y muestra el valor de conteo actual. Puede verlo funcionar presionando F5 para iniciar el emulador e iniciar la aplicación en modo de depuración.
Adición de componentes de interfaz de usuario
Agreguemos algunos componentes de la interfaz de usuario y hagámoslo más útil. Abra recursos/diseños y navegue hasta Main.axml.
Debería ver una representación visual del diseño de la actividad principal. Puede editar los diversos elementos de la interfaz de usuario haciendo clic en los elementos. Puede agregar elementos yendo a la caja de herramientas y seleccionando el elemento que desea agregar. Cambie el nombre del botón y agregue una vista de texto debajo del botón. La representación XML de su AXML debería ser similar a esto:
<?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>
El AXML hace referencia al archivo de recursos de cadenas, así que abra su resources/values/strings.xml y realice los siguientes cambios:
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="fivePM">It\'s 5PM Somewhere!</string> <string name="app_name">5PM Finder</string> </resources>
Ahora hemos construido el front-end AXML. Ahora vamos a conectarlo a algún código. Navegue a MainActivity.fs y realice los siguientes cambios en su función 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/") )
Reemplace fivepm.azurewebsites.net con la dirección URL de su propia implementación de API JSON. Ejecute la aplicación y haga clic en el botón en el emulador. En un momento, debería ver que la API de JSON regresa con el resultado de la API.
Analizando JSON
¡Casi estámos allí! En este momento, nuestra aplicación muestra el JSON sin procesar y es bastante ilegible. Entonces, el siguiente paso es analizar el JSON y generar una cadena más legible por humanos. Para analizar JSON, podemos usar la biblioteca Newtonsoft.JSON del servidor.
Navegue a su administrador de paquetes NuGet y busque Newtonsoft.JSON. Instale y vuelva al archivo MainActivity.fs. Importarlo agregando "abrir Newtonsoft.Json".
Ahora agregue el tipo TZInfo al proyecto. Podríamos reutilizar el TZInfo del servidor, pero dado que en realidad solo necesitamos dos de los campos, podemos definir un tipo personalizado aquí:
type TZInfo = { tzName:string localTime: string }
Agregue la definición de tipo arriba de la función principal, y ahora cambie la función principal de la siguiente manera:
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 )
Ahora, el resultado de la API JSON se deserializa en el objeto TZInfo y se usa para construir una cadena. Ejecute la aplicación y haga clic en el botón. Debería ver aparecer la cadena formateada en la pantalla.
Si bien esta aplicación es muy simple y quizás sin refinar, hemos creado una aplicación móvil que consume una API F# JSON, transforma los datos y se los muestra al usuario. Y lo hicimos todo en F#. Siéntase libre de jugar con los distintos controles y ver si puede mejorar el diseño.
¡Y ahí lo tenemos! Una aplicación móvil F# simple y una API JSON de F# y le dice al usuario dónde son las cinco en punto.
Terminando
Hoy analizamos la creación de una API web simple y una aplicación de Android simple usando solo F#, demostrando tanto la expresividad del lenguaje F# como la solidez del ecosistema F#. Sin embargo, apenas hemos arañado la superficie del desarrollo de F#, por lo que escribiré un par de publicaciones más para desarrollar lo que hemos discutido hoy. Además, espero que esta publicación lo haya inspirado a crear sus propias aplicaciones de F#.
Puede encontrar el código que usamos para este tutorial en GitHub.